├── .gitignore ├── renovate.json ├── examples ├── device.rs ├── traffic.rs └── speed.rs ├── Cargo.toml ├── src ├── device.rs └── lib.rs ├── .github └── workflows │ ├── test.yml │ ├── publish.yml │ └── rust-clippy.yml ├── LICENSE ├── README.zh-CN.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .history 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /examples/device.rs: -------------------------------------------------------------------------------- 1 | use netraffic::device::{get_default_device, get_device}; 2 | 3 | fn main() { 4 | let devices = get_device().unwrap(); 5 | println!("device: {:#?}", devices); 6 | 7 | let device = get_default_device().unwrap(); 8 | println!("default device: {:#?}", device); 9 | } 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "netraffic" 3 | version = "0.1.0" 4 | edition = "2021" 5 | categories = ["network-programming"] 6 | keywords = ["statistics", "traffic", "network", "port"] 7 | homepage = "https://github.com/ZingerLittleBee/netraffic" 8 | repository = "https://github.com/ZingerLittleBee/netraffic" 9 | description = "netraffic is a rust library that provides ability to statistics network traffic." 10 | readme = "README.md" 11 | license = "MIT" 12 | 13 | [dependencies] 14 | pcap = "0.9.2" -------------------------------------------------------------------------------- /src/device.rs: -------------------------------------------------------------------------------- 1 | use pcap::{Device, Error}; 2 | 3 | pub fn get_device() -> Result, Error> { 4 | Device::list() 5 | } 6 | 7 | pub fn get_default_device() -> Result { 8 | Device::lookup() 9 | } 10 | 11 | #[cfg(test)] 12 | mod test { 13 | use crate::device::get_default_device; 14 | 15 | use super::get_device; 16 | 17 | #[test] 18 | fn test_get_device() { 19 | assert!(get_device().unwrap().len() > 0); 20 | } 21 | 22 | #[test] 23 | fn test_get_default_device() { 24 | assert!(get_default_device().is_ok()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths-ignore: 7 | - '**/*.md' 8 | - LICENSE 9 | - '**/*.gitignore' 10 | - examples/** 11 | pull_request: 12 | branches: [main] 13 | 14 | workflow_dispatch: 15 | 16 | jobs: 17 | test: 18 | name: Rust Test 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 22 | - run: curl https://sh.rustup.rs -sSf | sudo sh -s -- -y 23 | - run: sudo apt-get -y update && sudo apt-get -y install libpcap-dev telnet 24 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 25 | - run: sudo ~/.cargo/bin/cargo test 26 | -------------------------------------------------------------------------------- /examples/traffic.rs: -------------------------------------------------------------------------------- 1 | use std::{thread, time::Duration}; 2 | 3 | use netraffic::{Filter, Traffic}; 4 | 5 | fn main() { 6 | let mut traffic = Traffic::new(); 7 | // rule look here: https://biot.com/capstats/bpf.html 8 | let rule1 = "port 443"; 9 | let rule2 = "src host 127.0.0.1"; 10 | traffic.add_listener(Filter::new("eth0".to_string(), rule1.to_string())); 11 | traffic.add_listener(Filter::new("eth0".to_string(), rule2.to_string())); 12 | loop { 13 | thread::sleep(Duration::from_millis(1000)); 14 | println!( 15 | "rule1: {}, traffic: {:#?} Bytes", 16 | rule1, 17 | traffic.get_data().get(rule1).unwrap().total 18 | ); 19 | println!( 20 | "rule2: {}, traffic: {:#?} Bytes", 21 | rule2, 22 | traffic.get_data().get(rule2).unwrap().total 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish CD 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish: 11 | name: Publish 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 🛎️ 15 | uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 16 | 17 | - name: Install stable toolchain 💿 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | profile: minimal 21 | toolchain: stable 22 | override: true 23 | 24 | - run: sudo apt-get -y update && sudo apt-get -y install libpcap-dev 25 | 26 | - name: Publish to crate 🎉 27 | run: cargo publish --token ${CRATES_TOKEN} 28 | env: 29 | CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} 30 | -------------------------------------------------------------------------------- /examples/speed.rs: -------------------------------------------------------------------------------- 1 | use std::{thread, time::Duration}; 2 | 3 | use netraffic::{Filter, Traffic}; 4 | 5 | fn main() { 6 | let mut traffic = Traffic::new(); 7 | let rule = "port 5000 or tcp port 443"; 8 | traffic.add_listener(Filter::new("eth0".to_string(), rule.to_string())); 9 | loop { 10 | let pre = traffic.get_data().get(rule).unwrap().total; 11 | thread::sleep(Duration::from_millis(1000)); 12 | let bytes = (traffic.get_data().get(rule).unwrap().total - pre) as f64; 13 | println!( 14 | "speed: {:.2?} {}/s", 15 | if bytes >= 1000.0 * 1000.0 { 16 | bytes / (1000.0 * 1000.0) 17 | } else if bytes >= 1000.0 { 18 | bytes / 1000.0 19 | } else { 20 | bytes 21 | }, 22 | if bytes >= 1000.0 * 1000.0 { 23 | "MB" 24 | } else if bytes >= 1000.0 { 25 | "KB" 26 | } else { 27 | "B" 28 | } 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 勤劳的小蜜蜂 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/rust-clippy.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # rust-clippy is a tool that runs a bunch of lints to catch common 6 | # mistakes in your Rust code and help improve your Rust code. 7 | # More details at https://github.com/rust-lang/rust-clippy 8 | # and https://rust-lang.github.io/rust-clippy/ 9 | 10 | name: rust-clippy analyze 11 | 12 | on: 13 | push: 14 | branches: [ main ] 15 | pull_request: 16 | # The branches below must be a subset of the branches above 17 | branches: [ main ] 18 | schedule: 19 | - cron: '16 4 * * 4' 20 | 21 | jobs: 22 | rust-clippy-analyze: 23 | name: Run rust-clippy analyzing 24 | runs-on: ubuntu-latest 25 | permissions: 26 | contents: read 27 | security-events: write 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4 31 | 32 | - name: Install Rust toolchain 33 | uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # v1 34 | with: 35 | profile: minimal 36 | toolchain: stable 37 | components: clippy 38 | override: true 39 | 40 | - name: Install required cargo 41 | run: cargo install clippy-sarif sarif-fmt 42 | 43 | - name: Run rust-clippy 44 | run: 45 | cargo clippy 46 | --all-features 47 | --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt 48 | continue-on-error: true 49 | 50 | - name: Upload analysis results to GitHub 51 | uses: github/codeql-action/upload-sarif@v2 52 | with: 53 | sarif_file: rust-clippy-results.sarif 54 | wait-for-processing: true 55 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | Language : [🇺🇸 English](./README.md) | 🇨🇳 简体中文 2 | 3 |

netraffic

4 |
5 | 6 | [![Build Status](https://img.shields.io/crates/v/netraffic?style=for-the-badge)](https://crates.io/crates/netraffic) 7 | ![Crates Downloads](https://img.shields.io/crates/d/netraffic?style=for-the-badge) 8 | ![Last Commit](https://img.shields.io/github/last-commit/ZingerLittleBee/netraffic?style=for-the-badge) 9 | 10 |
11 |
12 | 13 | [![Docs](https://img.shields.io/docsrs/netraffic?style=for-the-badge)](https://docs.rs/netraffic/0.1.0/netraffic/) 14 | [![GitHub Actions CI](https://img.shields.io/github/actions/workflow/status/ZingerLittleBee/netraffic/test.yml?style=for-the-badge)](https://github.com/ZingerLittleBee/netraffic/actions) 15 | [![LICENSE](https://img.shields.io/crates/l/netraffic?style=for-the-badge)](./LICENSE) 16 | 17 |
18 | 19 | ## 简介 20 | `netraffic` 是一个 `rust` 库,提供**统计网络流量**的功能。 21 | 22 | ## 先决条件 23 | ### `Windows` 24 | 下载 [WinPcap](http://www.winpcap.org/install/default.htm) 开发者包, 添加 `/Lib` 或 `/Lib/x64` 目录到系统环境变量中 25 | 26 | ### `Linux` 27 | 安装 `libpcap` 28 | 29 | Debian 系 Linux, 通过 `apt install libpcap-dev` 30 | 31 | ### `Mac OS X` 32 | Mac OS X 默认安装 `libpcap` 33 | 34 | ## 安装 35 | 1. 获取最新版本 -> https://crates.io/crates/netraffic 36 | 37 | 2. 添加到 `Cargo.toml` 38 | ```toml 39 | [dependencies] 40 | netraffic = "0.1.0" 41 | ``` 42 | 43 | 3. 用法 44 | ```rust 45 | use std::{thread, time::Duration}; 46 | use netraffic::{Filter, Traffic}; 47 | 48 | fn main() { 49 | let mut traffic = Traffic::new(); 50 | // rule look here: https://biot.com/capstats/bpf.html 51 | let rule1 = "port 443"; 52 | let rule2 = "src host 127.0.0.1"; 53 | // device: "any", just for linux mac 54 | traffic.add_listener(Filter::new("any".to_string(), rule1.to_string())); 55 | traffic.add_listener(Filter::new("any".to_string(), rule2.to_string())); 56 | loop { 57 | thread::sleep(Duration::from_millis(1000)); 58 | println!( 59 | "rule1: {}, traffic: {:#?} Bytes", 60 | rule1, 61 | traffic.get_data().get(rule1).unwrap().total 62 | ); 63 | println!( 64 | "rule2: {}, traffic: {:#?} Bytes", 65 | rule2, 66 | traffic.get_data().get(rule2).unwrap().total 67 | ); 68 | } 69 | } 70 | ``` 71 | > 查看更多 [Examples](#examples) 72 | 73 | ## 例子 74 | [🖥 获取网卡列表](./examples/device.rs) 75 | 76 | [🚥 流量统计](./examples/traffic.rs) 77 | 78 | [🚄 实时网速](./examples/speed.rs) 79 | 80 | ## 总览 81 | struct -> [Traffic](#traffic) · [Filter](#filter) · [Snapshot](#snapshot) 82 | 83 | mod (`device`) -> [get_device](#get_device) · [get_default_device](#get_default_device) 84 | 85 | ## 文档 86 | ### `Traffic` 87 | ```rust 88 | impl Traffic { 89 | /// 初始化 90 | pub fn new() -> Self 91 | /// 根据 filter 添加监听器 92 | pub fn add_listener(&mut self, filter: Filter) 93 | /// 通过 filter.rule 移除监听器 94 | pub fn remove_listener(&self, rule: String) 95 | /// 挂起 filter.rule 的监听器 96 | pub fn suspend_listener(&self, rule: String) 97 | /// 恢复 filter.rule 的监听器 98 | pub fn resume_listener(&self, rule: String) 99 | /// 阻塞线程直到获取到 Map 键值对 100 | pub fn get_data(&self) -> HashMap 101 | /// 尝试获 Map 键值对 102 | /// 获取失败则返回 None 103 | pub fn try_get_data(&self) -> Option> 104 | } 105 | ``` 106 | 107 | ### `Filter` 108 | ```rust 109 | #[derive(Debug, Clone)] 110 | pub struct Filter { 111 | /// 网卡名称 112 | pub device: String, 113 | /// 过滤规则 114 | /// BPF 规则: https://biot.com/capstats/bpf.html 115 | pub rule: String, 116 | /// 是否立即模式, 默认为 true 117 | /// https://www.tcpdump.org/manpages/pcap_set_immediate_mode.3pcap.html 118 | pub immediate_mode: bool, 119 | } 120 | 121 | /// 初始化, 默认 immediate_mode = true 122 | Filter::new("eth0".to_string(), "tcp port 80".to_string()); 123 | /// or 设置 immediate_mode 字段 124 | Filter { 125 | device: "eth0".to_string(), 126 | rule: "tcp port 80".to_string(), 127 | immediate_mode: true, 128 | } 129 | ``` 130 | 131 | ### `Snapshot` 132 | ```rust 133 | #[derive(Debug, Clone, Copy)] 134 | pub struct Snapshot { 135 | /// add_listener 之后的总 Byte 136 | pub total: u64, 137 | /// 最新一包数据的 Byte 138 | pub len: u64, 139 | /// 最新一包数据的时间戳 140 | pub timestamp: u64, 141 | } 142 | ``` 143 | 144 | ### `get_device` 145 | ```rust 146 | /// 获取所有网卡 147 | pub fn get_device() -> Result, Error> 148 | ``` 149 | 150 | ### `get_default_device` 151 | ```rust 152 | /// 获取默认网卡 153 | pub fn get_default_device() -> Result 154 | ``` 155 | 156 | ## 感谢 157 | [pcap](https://github.com/rust-pcap/pcap) 158 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Language : 🇺🇸 English | [🇨🇳 简体中文](./README.zh-CN.md) 2 | 3 |

netraffic

4 |
5 | 6 | [![Build Status](https://img.shields.io/crates/v/netraffic?style=for-the-badge)](https://crates.io/crates/netraffic) 7 | ![Crates Downloads](https://img.shields.io/crates/d/netraffic?style=for-the-badge) 8 | ![Last Commit](https://img.shields.io/github/last-commit/ZingerLittleBee/netraffic?style=for-the-badge) 9 | 10 |
11 |
12 | 13 | [![Docs](https://img.shields.io/docsrs/netraffic?style=for-the-badge)](https://docs.rs/netraffic/0.1.0/netraffic/) 14 | [![GitHub Actions CI](https://img.shields.io/github/actions/workflow/status/ZingerLittleBee/netraffic/test.yml?style=for-the-badge)](https://github.com/ZingerLittleBee/netraffic/actions) 15 | [![LICENSE](https://img.shields.io/crates/l/netraffic?style=for-the-badge)](./LICENSE) 16 | 17 |
18 | 19 | ## Overview 20 | netraffic is a rust library that provides ability to **statistics network traffic**. 21 | 22 | ## Prerequisites 23 | ### Windows 24 | Download the [WinPcap](http://www.winpcap.org/install/default.htm) Developer's Pack. Add the `/Lib` or `/Lib/x64` folder to your LIB environment variable. 25 | 26 | ### Linux 27 | Install `libpcap` 28 | 29 | On Debian based Linux, `apt install libpcap-dev` 30 | 31 | ### Mac OS X 32 | libpcap should be installed on Mac OS X by default. 33 | 34 | ## Installation 35 | 1. Get the latest version -> https://crates.io/crates/netraffic 36 | 37 | 2. Add the dependent 38 | ```toml 39 | [dependencies] 40 | netraffic = "0.1.0" 41 | ``` 42 | 43 | 3. Usage 44 | ```rust 45 | use std::{thread, time::Duration}; 46 | use netraffic::{Filter, Traffic}; 47 | 48 | fn main() { 49 | let mut traffic = Traffic::new(); 50 | // rule look here: https://biot.com/capstats/bpf.html 51 | let rule1 = "port 443"; 52 | let rule2 = "src host 127.0.0.1"; 53 | traffic.add_listener(Filter::new("eth0".to_string(), rule1.to_string())); 54 | traffic.add_listener(Filter::new("eth0".to_string(), rule2.to_string())); 55 | loop { 56 | thread::sleep(Duration::from_millis(1000)); 57 | println!( 58 | "rule1: {}, traffic: {:#?} Bytes", 59 | rule1, 60 | traffic.get_data().get(rule1).unwrap().total 61 | ); 62 | println!( 63 | "rule2: {}, traffic: {:#?} Bytes", 64 | rule2, 65 | traffic.get_data().get(rule2).unwrap().total 66 | ); 67 | } 68 | } 69 | ``` 70 | > Learn More [Examples](#examples) 71 | 72 | ## Examples 73 | [🖥 Get network interface device](./examples/device.rs) 74 | 75 | [🚥 Statistical traffic](./examples/traffic.rs) 76 | 77 | [🚄 Calculate network speed](./examples/speed.rs) 78 | 79 | ## Goods 80 | struct -> [Traffic](#traffic) · [Filter](#filter) · [Snapshot](#snapshot) 81 | 82 | mod (`device`) -> [get_device](#get_device) · [get_default_device](#get_default_device) 83 | 84 | ## Documentation 85 | ### `Traffic` 86 | ```rust 87 | impl Traffic { 88 | /// Init traffic 89 | pub fn new() -> Self 90 | /// Add a new filter to the traffic data center. 91 | pub fn add_listener(&mut self, filter: Filter) 92 | /// Remove a filter from the traffic data center. 93 | pub fn remove_listener(&self, rule: String) 94 | /// Suspend a listener by rule. 95 | pub fn suspend_listener(&self, rule: String) 96 | /// Resume a listener by rule. 97 | pub fn resume_listener(&self, rule: String) 98 | /// Get the traffic snapshot, until Rwlock is free. 99 | pub fn get_data(&self) -> HashMap 100 | /// Try to get the traffic snapshot. 101 | /// if Rwlock is locked, return None. 102 | pub fn try_get_data(&self) -> Option> 103 | } 104 | ``` 105 | 106 | ### `Filter` 107 | ```rust 108 | #[derive(Debug, Clone)] 109 | pub struct Filter { 110 | /// Name of network interface 111 | pub device: String, 112 | /// Filtering rules 113 | /// BPF : https://biot.com/capstats/bpf.html 114 | pub rule: String, 115 | /// Whether the mode is immediately modeled, the default true 116 | /// https://www.tcpdump.org/manpages/pcap_set_immediate_mode.3pcap.html 117 | pub immediate_mode: bool, 118 | } 119 | 120 | /// Init filter, the default immediate_mode = true 121 | Filter::new("eth0".to_string(), "tcp port 80".to_string()); 122 | /// or set immediate_mode field 123 | Filter { 124 | device: "eth0".to_string(), 125 | rule: "tcp port 80".to_string(), 126 | immediate_mode: true, 127 | } 128 | ``` 129 | 130 | ### `Snapshot` 131 | ```rust 132 | #[derive(Debug, Clone, Copy)] 133 | pub struct Snapshot { 134 | /// The total byte after add_listener 135 | pub total: u64, 136 | /// The latest package of data byte 137 | pub len: u64, 138 | /// The latest package of data timestamp 139 | pub timestamp: u64, 140 | } 141 | ``` 142 | 143 | ### `get_device` 144 | ```rust 145 | /// Get all network interface 146 | pub fn get_device() -> Result, Error> 147 | ``` 148 | 149 | ### `get_default_device` 150 | ```rust 151 | /// Get default network interface 152 | pub fn get_default_device() -> Result 153 | ``` 154 | 155 | ## Thanks 156 | [pcap](https://github.com/rust-pcap/pcap) 157 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod device; 2 | 3 | use std::{ 4 | collections::HashMap, 5 | sync::{ 6 | mpsc::{self, Sender, TryRecvError}, 7 | Arc, RwLock, 8 | }, 9 | thread, 10 | }; 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct Filter { 14 | pub device: String, 15 | pub rule: String, 16 | pub immediate_mode: bool, 17 | } 18 | 19 | impl Filter { 20 | pub fn new(device: String, rule: String) -> Self { 21 | Filter { 22 | device, 23 | rule, 24 | immediate_mode: true, 25 | } 26 | } 27 | } 28 | 29 | #[derive(Debug, Clone, Copy)] 30 | pub struct Snapshot { 31 | pub total: u64, 32 | pub len: u64, 33 | pub timestamp: u64, 34 | } 35 | 36 | impl Default for Snapshot { 37 | fn default() -> Self { 38 | Snapshot { 39 | total: 0, 40 | timestamp: 0, 41 | len: 0, 42 | } 43 | } 44 | } 45 | 46 | #[derive(Debug, Clone, Copy)] 47 | enum Action { 48 | Suspend, 49 | Resume, 50 | Stop, 51 | } 52 | 53 | #[derive(Debug, Clone)] 54 | pub struct Traffic { 55 | data_center: Arc>>, 56 | signal: HashMap>, 57 | } 58 | 59 | impl Traffic { 60 | /// Init traffic 61 | pub fn new() -> Self { 62 | Traffic { 63 | data_center: Arc::new(RwLock::new(HashMap::new())), 64 | signal: HashMap::new(), 65 | } 66 | } 67 | 68 | /// Add a new filter to the traffic data center. 69 | pub fn add_listener(&mut self, filter: Filter) { 70 | // init map 71 | self.data_center 72 | .write() 73 | .unwrap() 74 | .insert(filter.rule.clone(), Snapshot::default()); 75 | let (rule, tx) = self.register(filter); 76 | self.signal.insert(rule, tx); 77 | } 78 | 79 | /// Remove a filter from the traffic data center. 80 | pub fn remove_listener(&self, rule: String) { 81 | match self.signal.get(&rule) { 82 | Some(s) => { 83 | let _ = s.send(Action::Stop); 84 | } 85 | None => {} 86 | } 87 | } 88 | 89 | /// Suspend a listener by rule. 90 | pub fn suspend_listener(&self, rule: String) { 91 | match self.signal.get(&rule) { 92 | Some(s) => { 93 | let _ = s.send(Action::Suspend); 94 | } 95 | None => {} 96 | } 97 | } 98 | 99 | /// Resume a listener by rule. 100 | pub fn resume_listener(&self, rule: String) { 101 | match self.signal.get(&rule) { 102 | Some(s) => { 103 | let _ = s.send(Action::Resume); 104 | } 105 | None => {} 106 | } 107 | } 108 | 109 | /// Get the traffic snapshot, until Rwlock is free. 110 | pub fn get_data(&self) -> HashMap { 111 | self.data_center.read().expect("get data failed").clone() 112 | } 113 | 114 | /// Try to get the traffic snapshot. 115 | /// if Rwlock is locked, return None. 116 | pub fn try_get_data(&self) -> Option> { 117 | match self.data_center.try_read() { 118 | Ok(dc) => Some(dc.clone()), 119 | Err(_) => None, 120 | } 121 | } 122 | 123 | fn register(&self, filter: Filter) -> (String, Sender) { 124 | let total = self.data_center.clone(); 125 | // Control the thread 126 | let (tx, rx) = mpsc::channel::(); 127 | let rule = filter.rule.clone(); 128 | thread::spawn(move || { 129 | let mut cap = pcap::Capture::from_device(&filter.device[..]) 130 | .unwrap() 131 | .immediate_mode(filter.immediate_mode) 132 | .open() 133 | .unwrap(); 134 | // filter the packet by BPF syntax 135 | // BPF syntax, look at https://biot.com/capstats/bpf.html 136 | cap.filter(&filter.rule[..], true) 137 | .expect("set filter failed"); 138 | // (index, Snapshot) 139 | let mut i: (u32, Snapshot) = (0, Default::default()); 140 | while let Ok(packet) = cap.next() { 141 | // Check channel signal 142 | match rx.try_recv() { 143 | Ok(action) => match action { 144 | Action::Suspend => { 145 | match rx.recv() { 146 | Ok(inner_action) => match inner_action { 147 | Action::Stop => break, 148 | // Resume 149 | _ => {} 150 | }, 151 | Err(_) => break, 152 | } 153 | } 154 | Action::Stop => { 155 | break; 156 | } 157 | _ => {} 158 | }, 159 | Err(TryRecvError::Disconnected) => { 160 | break; 161 | } 162 | Err(TryRecvError::Empty) => {} 163 | } 164 | i.0 += 1; 165 | i.1.total += packet.header.len as u64; 166 | i.1.len = packet.header.len as u64; 167 | i.1.timestamp += packet.header.ts.tv_sec as u64; 168 | // Update data center 169 | // `i.0 % 2` to avoid the Rwlock is always locked. 170 | if i.0 % 2 == 0 { 171 | let mut t = total.write().unwrap(); 172 | t.insert(String::from(&filter.rule[..]), i.1); 173 | } 174 | } 175 | }); 176 | (rule, tx) 177 | } 178 | } 179 | 180 | #[cfg(test)] 181 | mod test { 182 | use std::{process::Command, thread, time::Duration}; 183 | 184 | use crate::{Filter, Traffic}; 185 | 186 | #[test] 187 | fn test_get_data() { 188 | let mut traffic = Traffic::new(); 189 | traffic.add_listener(Filter::new(String::from("any"), String::from("port 443"))); 190 | Command::new("telnet") 191 | .args(["github.com", "443"]) 192 | .output() 193 | .unwrap(); 194 | 195 | thread::sleep(Duration::from_millis(100)); 196 | assert!(traffic.get_data().len() > 0); 197 | assert!(traffic.get_data().get("port 443").unwrap().len > 0); 198 | assert!(traffic.get_data().get("port 443").unwrap().total > 0); 199 | assert!(traffic.try_get_data().unwrap().len() > 0); 200 | } 201 | 202 | #[test] 203 | fn test_suspend_resume_listener() { 204 | let rule = "port 443"; 205 | let mut traffic = Traffic::new(); 206 | traffic.add_listener(Filter::new(String::from("any"), String::from(rule))); 207 | Command::new("telnet") 208 | .args(["github.com", "443"]) 209 | .output() 210 | .unwrap(); 211 | thread::sleep(Duration::from_millis(1000)); 212 | traffic.suspend_listener(rule.to_string()); 213 | let total = traffic.get_data().get(rule).unwrap().total; 214 | Command::new("telnet") 215 | .args(["github.com", "443"]) 216 | .output() 217 | .unwrap(); 218 | thread::sleep(Duration::from_millis(1000)); 219 | assert_eq!(traffic.get_data().get(rule).unwrap().total, total); 220 | traffic.resume_listener(rule.to_string()); 221 | Command::new("telnet") 222 | .args(["github.com", "443"]) 223 | .output() 224 | .unwrap(); 225 | thread::sleep(Duration::from_millis(1000)); 226 | assert!(traffic.get_data().get(rule).unwrap().total > total); 227 | } 228 | 229 | #[test] 230 | fn test_remove_listener() { 231 | let rule = "port 443"; 232 | let mut traffic = Traffic::new(); 233 | traffic.add_listener(Filter::new(String::from("any"), String::from(rule))); 234 | Command::new("telnet") 235 | .args(["github.com", "443"]) 236 | .output() 237 | .unwrap(); 238 | thread::sleep(Duration::from_millis(1000)); 239 | traffic.remove_listener(rule.to_string()); 240 | let length = traffic.get_data().get(rule).unwrap().len; 241 | Command::new("telnet") 242 | .args(["github.com", "443"]) 243 | .output() 244 | .unwrap(); 245 | thread::sleep(Duration::from_millis(1000)); 246 | assert_eq!(traffic.get_data().get(rule).unwrap().len, length); 247 | } 248 | } 249 | --------------------------------------------------------------------------------