netflower 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- netflower-1.0.0/LICENSE +21 -0
- netflower-1.0.0/PKG-INFO +178 -0
- netflower-1.0.0/README.md +129 -0
- netflower-1.0.0/netflower/__init__.py +5 -0
- netflower-1.0.0/netflower/_capture.py +222 -0
- netflower-1.0.0/netflower/_constants.py +18 -0
- netflower-1.0.0/netflower/_convert.py +248 -0
- netflower-1.0.0/netflower/_flow.py +458 -0
- netflower-1.0.0/netflower/_libpcap.py +101 -0
- netflower-1.0.0/netflower/_parser.py +55 -0
- netflower-1.0.0/netflower/_session.py +83 -0
- netflower-1.0.0/netflower/_stats.py +50 -0
- netflower-1.0.0/netflower/_writer.py +47 -0
- netflower-1.0.0/netflower.egg-info/PKG-INFO +178 -0
- netflower-1.0.0/netflower.egg-info/SOURCES.txt +25 -0
- netflower-1.0.0/netflower.egg-info/dependency_links.txt +1 -0
- netflower-1.0.0/netflower.egg-info/requires.txt +5 -0
- netflower-1.0.0/netflower.egg-info/top_level.txt +1 -0
- netflower-1.0.0/pyproject.toml +42 -0
- netflower-1.0.0/setup.cfg +4 -0
- netflower-1.0.0/tests/test_capture.py +102 -0
- netflower-1.0.0/tests/test_convert.py +197 -0
- netflower-1.0.0/tests/test_flow.py +90 -0
- netflower-1.0.0/tests/test_parser.py +107 -0
- netflower-1.0.0/tests/test_session.py +76 -0
- netflower-1.0.0/tests/test_stats.py +52 -0
- netflower-1.0.0/tests/test_writer.py +55 -0
netflower-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Luiz Henrique
|
|
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.
|
netflower-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: netflower
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: High-performance network flow extractor with live capture support for edge devices
|
|
5
|
+
Author-email: Luiz Henrique <luizlinkezio@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Luiz Henrique
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Repository, https://github.com/luiz-linkezio/pcapflower
|
|
29
|
+
Keywords: pcap,pcapng,network,flow,CICFlowMeter,ids,intrusion detection,live capture
|
|
30
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
31
|
+
Classifier: Intended Audience :: Science/Research
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Topic :: System :: Networking :: Monitoring
|
|
39
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
40
|
+
Classifier: Operating System :: OS Independent
|
|
41
|
+
Requires-Python: >=3.10
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Requires-Dist: dpkt>=1.9.8
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: pytest; extra == "dev"
|
|
47
|
+
Requires-Dist: ruff; extra == "dev"
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
|
|
50
|
+
<p align="center">
|
|
51
|
+
<img src="assets/icon.png" alt="netflower" width="128" />
|
|
52
|
+
</p>
|
|
53
|
+
|
|
54
|
+
<h1 align="center">netflower</h1>
|
|
55
|
+
|
|
56
|
+
<p align="center">High-performance network flow extractor for edge devices.</p>
|
|
57
|
+
|
|
58
|
+
**netflower** extracts bidirectional network flows — either from a live interface or from `.pcap` / `.pcapng` files — and produces 82 features compatible with the [CICFlowMeter](https://www.unb.ca/cic/research/applications.html) feature set.
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
- **Live capture** — capture flows in real time from a network interface; flows are emitted only when complete (TCP FIN/RST or idle timeout), never cut by an arbitrary boundary
|
|
63
|
+
- **PCAP flows to CSV** — convert `.pcap` / `.pcapng` files to flow-based CSV, with optional parallel processing
|
|
64
|
+
- **Edge-optimized** — `dpkt` for packet parsing (~10–25× faster than Scapy on ARM), Welford's online algorithm for O(1) memory per flow, batch-buffered CSV output
|
|
65
|
+
- **No extra pip dependencies** for live capture — libpcap is accessed via `ctypes`
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install netflower
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Live capture requires libpcap on the system:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Linux
|
|
77
|
+
sudo apt install libpcap-dev
|
|
78
|
+
|
|
79
|
+
# macOS
|
|
80
|
+
brew install libpcap
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Usage
|
|
84
|
+
|
|
85
|
+
### Live capture
|
|
86
|
+
|
|
87
|
+
Captures packets from a network interface and emits each flow the moment it completes.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from netflower import capture_live
|
|
91
|
+
|
|
92
|
+
def on_flow(flow):
|
|
93
|
+
print(flow["src_ip"], flow["dst_ip"], flow["flow_duration"])
|
|
94
|
+
|
|
95
|
+
handle = capture_live("eth0", on_flow=on_flow)
|
|
96
|
+
handle.start()
|
|
97
|
+
|
|
98
|
+
# ... rest of your program ...
|
|
99
|
+
|
|
100
|
+
handle.stop()
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
As a context manager:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
with capture_live("eth0", on_flow=on_flow) as handle:
|
|
107
|
+
handle.start()
|
|
108
|
+
import time; time.sleep(60)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
> **Note:** live capture requires root or `CAP_NET_RAW` privilege.
|
|
112
|
+
|
|
113
|
+
### PCAP flows to CSV
|
|
114
|
+
|
|
115
|
+
Converts a `.pcap` or `.pcapng` file into a flow-based CSV.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from netflower import convert_pcap_to_csv
|
|
119
|
+
|
|
120
|
+
n = convert_pcap_to_csv("capture.pcap", "flows.csv")
|
|
121
|
+
print(f"Extracted {n} flows")
|
|
122
|
+
|
|
123
|
+
# Parallel — use all available CPUs
|
|
124
|
+
n = convert_pcap_to_csv("capture.pcap", "flows.csv", n_jobs=-1)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## API
|
|
128
|
+
|
|
129
|
+
### `capture_live(interface, on_flow, **kwargs) → CaptureHandle`
|
|
130
|
+
|
|
131
|
+
| Parameter | Default | Description |
|
|
132
|
+
|-----------|---------|-------------|
|
|
133
|
+
| `interface` | — | Network interface name (e.g. `"eth0"`) |
|
|
134
|
+
| `on_flow` | — | Callable receiving a flow `dict` when a flow completes |
|
|
135
|
+
| `idle_timeout` | `30.0` | Seconds of inactivity before a flow is emitted |
|
|
136
|
+
| `flow_timeout` | `120.0` | Absolute max flow duration before forced emit |
|
|
137
|
+
| `save_pcap` | `False` | Save raw packets of each completed flow to a `.pcap` file |
|
|
138
|
+
| `pcap_dir` | `None` | Directory for `.pcap` files (required when `save_pcap=True`) |
|
|
139
|
+
|
|
140
|
+
`CaptureHandle` exposes `.start()`, `.stop()`, and context manager support.
|
|
141
|
+
|
|
142
|
+
### `convert_pcap_to_csv(input_path, output_path, **kwargs) → int`
|
|
143
|
+
|
|
144
|
+
| Parameter | Default | Description |
|
|
145
|
+
|-----------|---------|-------------|
|
|
146
|
+
| `input_path` | — | Path to `.pcap` or `.pcapng` file |
|
|
147
|
+
| `output_path` | — | Path for the output `.csv` (created or overwritten) |
|
|
148
|
+
| `flow_timeout` | `120.0` | Seconds of inactivity before a flow is evicted |
|
|
149
|
+
| `gc_interval` | `1000` | Run idle-flow GC every N packets |
|
|
150
|
+
| `buffer_rows` | `500` | Rows buffered in memory before flushing to disk |
|
|
151
|
+
| `n_jobs` | `1` | Worker processes. `-1` uses all available CPUs |
|
|
152
|
+
|
|
153
|
+
Returns the number of flow rows written.
|
|
154
|
+
|
|
155
|
+
## Flow features
|
|
156
|
+
|
|
157
|
+
Both APIs produce the same **82 features** per flow:
|
|
158
|
+
|
|
159
|
+
- Flow identity: source/destination IP, port, protocol, timestamp
|
|
160
|
+
- Duration, bytes/s, and packets/s (forward, backward, combined)
|
|
161
|
+
- Packet length statistics (mean, std, min, max, variance)
|
|
162
|
+
- Inter-arrival time statistics (flow, forward, backward)
|
|
163
|
+
- TCP flag counts (FIN, SYN, RST, PSH, ACK, URG, ECE, CWR)
|
|
164
|
+
- Active/idle period statistics
|
|
165
|
+
- Bulk transfer metrics (forward and backward)
|
|
166
|
+
- Subflow metrics
|
|
167
|
+
- Initial TCP window sizes
|
|
168
|
+
|
|
169
|
+
## Supported formats
|
|
170
|
+
|
|
171
|
+
- **pcap** — standard libpcap format
|
|
172
|
+
- **pcapng** — next-generation capture format
|
|
173
|
+
|
|
174
|
+
Only **IPv4 TCP and UDP** flows are extracted; other protocols are silently skipped.
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/icon.png" alt="netflower" width="128" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">netflower</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">High-performance network flow extractor for edge devices.</p>
|
|
8
|
+
|
|
9
|
+
**netflower** extracts bidirectional network flows — either from a live interface or from `.pcap` / `.pcapng` files — and produces 82 features compatible with the [CICFlowMeter](https://www.unb.ca/cic/research/applications.html) feature set.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Live capture** — capture flows in real time from a network interface; flows are emitted only when complete (TCP FIN/RST or idle timeout), never cut by an arbitrary boundary
|
|
14
|
+
- **PCAP flows to CSV** — convert `.pcap` / `.pcapng` files to flow-based CSV, with optional parallel processing
|
|
15
|
+
- **Edge-optimized** — `dpkt` for packet parsing (~10–25× faster than Scapy on ARM), Welford's online algorithm for O(1) memory per flow, batch-buffered CSV output
|
|
16
|
+
- **No extra pip dependencies** for live capture — libpcap is accessed via `ctypes`
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install netflower
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Live capture requires libpcap on the system:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Linux
|
|
28
|
+
sudo apt install libpcap-dev
|
|
29
|
+
|
|
30
|
+
# macOS
|
|
31
|
+
brew install libpcap
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Live capture
|
|
37
|
+
|
|
38
|
+
Captures packets from a network interface and emits each flow the moment it completes.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from netflower import capture_live
|
|
42
|
+
|
|
43
|
+
def on_flow(flow):
|
|
44
|
+
print(flow["src_ip"], flow["dst_ip"], flow["flow_duration"])
|
|
45
|
+
|
|
46
|
+
handle = capture_live("eth0", on_flow=on_flow)
|
|
47
|
+
handle.start()
|
|
48
|
+
|
|
49
|
+
# ... rest of your program ...
|
|
50
|
+
|
|
51
|
+
handle.stop()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
As a context manager:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
with capture_live("eth0", on_flow=on_flow) as handle:
|
|
58
|
+
handle.start()
|
|
59
|
+
import time; time.sleep(60)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> **Note:** live capture requires root or `CAP_NET_RAW` privilege.
|
|
63
|
+
|
|
64
|
+
### PCAP flows to CSV
|
|
65
|
+
|
|
66
|
+
Converts a `.pcap` or `.pcapng` file into a flow-based CSV.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from netflower import convert_pcap_to_csv
|
|
70
|
+
|
|
71
|
+
n = convert_pcap_to_csv("capture.pcap", "flows.csv")
|
|
72
|
+
print(f"Extracted {n} flows")
|
|
73
|
+
|
|
74
|
+
# Parallel — use all available CPUs
|
|
75
|
+
n = convert_pcap_to_csv("capture.pcap", "flows.csv", n_jobs=-1)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## API
|
|
79
|
+
|
|
80
|
+
### `capture_live(interface, on_flow, **kwargs) → CaptureHandle`
|
|
81
|
+
|
|
82
|
+
| Parameter | Default | Description |
|
|
83
|
+
|-----------|---------|-------------|
|
|
84
|
+
| `interface` | — | Network interface name (e.g. `"eth0"`) |
|
|
85
|
+
| `on_flow` | — | Callable receiving a flow `dict` when a flow completes |
|
|
86
|
+
| `idle_timeout` | `30.0` | Seconds of inactivity before a flow is emitted |
|
|
87
|
+
| `flow_timeout` | `120.0` | Absolute max flow duration before forced emit |
|
|
88
|
+
| `save_pcap` | `False` | Save raw packets of each completed flow to a `.pcap` file |
|
|
89
|
+
| `pcap_dir` | `None` | Directory for `.pcap` files (required when `save_pcap=True`) |
|
|
90
|
+
|
|
91
|
+
`CaptureHandle` exposes `.start()`, `.stop()`, and context manager support.
|
|
92
|
+
|
|
93
|
+
### `convert_pcap_to_csv(input_path, output_path, **kwargs) → int`
|
|
94
|
+
|
|
95
|
+
| Parameter | Default | Description |
|
|
96
|
+
|-----------|---------|-------------|
|
|
97
|
+
| `input_path` | — | Path to `.pcap` or `.pcapng` file |
|
|
98
|
+
| `output_path` | — | Path for the output `.csv` (created or overwritten) |
|
|
99
|
+
| `flow_timeout` | `120.0` | Seconds of inactivity before a flow is evicted |
|
|
100
|
+
| `gc_interval` | `1000` | Run idle-flow GC every N packets |
|
|
101
|
+
| `buffer_rows` | `500` | Rows buffered in memory before flushing to disk |
|
|
102
|
+
| `n_jobs` | `1` | Worker processes. `-1` uses all available CPUs |
|
|
103
|
+
|
|
104
|
+
Returns the number of flow rows written.
|
|
105
|
+
|
|
106
|
+
## Flow features
|
|
107
|
+
|
|
108
|
+
Both APIs produce the same **82 features** per flow:
|
|
109
|
+
|
|
110
|
+
- Flow identity: source/destination IP, port, protocol, timestamp
|
|
111
|
+
- Duration, bytes/s, and packets/s (forward, backward, combined)
|
|
112
|
+
- Packet length statistics (mean, std, min, max, variance)
|
|
113
|
+
- Inter-arrival time statistics (flow, forward, backward)
|
|
114
|
+
- TCP flag counts (FIN, SYN, RST, PSH, ACK, URG, ECE, CWR)
|
|
115
|
+
- Active/idle period statistics
|
|
116
|
+
- Bulk transfer metrics (forward and backward)
|
|
117
|
+
- Subflow metrics
|
|
118
|
+
- Initial TCP window sizes
|
|
119
|
+
|
|
120
|
+
## Supported formats
|
|
121
|
+
|
|
122
|
+
- **pcap** — standard libpcap format
|
|
123
|
+
- **pcapng** — next-generation capture format
|
|
124
|
+
|
|
125
|
+
Only **IPv4 TCP and UDP** flows are extracted; other protocols are silently skipped.
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Live packet capture using system libpcap via ctypes.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from netflower import capture_live
|
|
6
|
+
|
|
7
|
+
handle = capture_live("eth0", on_flow=lambda flow: print(flow), idle_timeout=30)
|
|
8
|
+
handle.start()
|
|
9
|
+
# ... do work ...
|
|
10
|
+
handle.stop()
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import ctypes
|
|
14
|
+
import threading
|
|
15
|
+
|
|
16
|
+
from ._libpcap import (
|
|
17
|
+
PcapHandler_cb, PktHdr,
|
|
18
|
+
pcap_open_live, pcap_loop, pcap_breakloop, pcap_close,
|
|
19
|
+
pcap_dump_open, pcap_dump, pcap_dump_close,
|
|
20
|
+
)
|
|
21
|
+
from ._parser import _parse_packet
|
|
22
|
+
from ._session import FlowSession
|
|
23
|
+
from ._constants import FLOW_TIMEOUT
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CallbackWriter:
|
|
27
|
+
"""Writer that calls on_flow(dict) instead of writing to a CSV file."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, on_flow: callable) -> None:
|
|
30
|
+
self._on_flow = on_flow
|
|
31
|
+
|
|
32
|
+
def write(self, flow_dict: dict) -> None:
|
|
33
|
+
self._on_flow(flow_dict)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _PcapSavingWriter:
|
|
37
|
+
"""
|
|
38
|
+
Wraps on_flow callable. When a flow is emitted, also dumps its raw packets
|
|
39
|
+
to a .pcap file in pcap_dir before clearing the buffer.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, on_flow: callable, pcap_dir: str, pcap_handle) -> None:
|
|
43
|
+
self._on_flow = on_flow
|
|
44
|
+
self._pcap_dir = pcap_dir
|
|
45
|
+
self._pcap_handle = pcap_handle
|
|
46
|
+
# flow_key -> list of (PktHdr, bytes)
|
|
47
|
+
self._buffers: dict[tuple, list] = {}
|
|
48
|
+
|
|
49
|
+
def write(self, flow_dict: dict) -> None:
|
|
50
|
+
key = (
|
|
51
|
+
flow_dict.get("protocol"),
|
|
52
|
+
flow_dict.get("src_ip"),
|
|
53
|
+
flow_dict.get("dst_ip"),
|
|
54
|
+
flow_dict.get("src_port"),
|
|
55
|
+
flow_dict.get("dst_port"),
|
|
56
|
+
)
|
|
57
|
+
packets = self._buffers.pop(key, [])
|
|
58
|
+
if packets:
|
|
59
|
+
ts_us = int(flow_dict.get("timestamp", 0) * 1_000_000)
|
|
60
|
+
fname = (
|
|
61
|
+
f"{self._pcap_dir}/"
|
|
62
|
+
f"{flow_dict.get('src_ip')}-{flow_dict.get('src_port')}-"
|
|
63
|
+
f"{flow_dict.get('dst_ip')}-{flow_dict.get('dst_port')}-"
|
|
64
|
+
f"{flow_dict.get('protocol')}-{ts_us}.pcap"
|
|
65
|
+
).encode()
|
|
66
|
+
dumper = pcap_dump_open(self._pcap_handle, fname)
|
|
67
|
+
if dumper:
|
|
68
|
+
for hdr, data in packets:
|
|
69
|
+
pcap_dump(dumper, ctypes.byref(hdr), data)
|
|
70
|
+
pcap_dump_close(dumper)
|
|
71
|
+
self._on_flow(flow_dict)
|
|
72
|
+
|
|
73
|
+
def buffer_packet(self, key: tuple, hdr: PktHdr, data: bytes) -> None:
|
|
74
|
+
if key not in self._buffers:
|
|
75
|
+
self._buffers[key] = []
|
|
76
|
+
self._buffers[key].append((hdr, data))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class CaptureHandle:
|
|
80
|
+
"""
|
|
81
|
+
Handle returned by capture_live(). Controls start/stop of capture thread.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
interface: Network interface name as bytes (e.g. b"eth0").
|
|
86
|
+
on_flow: Callable receiving a flow dict when a flow completes.
|
|
87
|
+
idle_timeout: Seconds of inactivity before a flow is emitted.
|
|
88
|
+
flow_timeout: Absolute max flow duration before forced emit.
|
|
89
|
+
save_pcap: If True, save raw packets of each completed flow to pcap_dir.
|
|
90
|
+
pcap_dir: Directory path for .pcap files. Required when save_pcap=True.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
_GC_INTERVAL = 1000
|
|
94
|
+
|
|
95
|
+
def __init__(
|
|
96
|
+
self,
|
|
97
|
+
interface: bytes,
|
|
98
|
+
on_flow: callable,
|
|
99
|
+
idle_timeout: float,
|
|
100
|
+
flow_timeout: float,
|
|
101
|
+
save_pcap: bool,
|
|
102
|
+
pcap_dir: str | None,
|
|
103
|
+
) -> None:
|
|
104
|
+
if save_pcap and pcap_dir is None:
|
|
105
|
+
raise ValueError("pcap_dir is required when save_pcap=True")
|
|
106
|
+
self._interface = interface
|
|
107
|
+
self._on_flow = on_flow
|
|
108
|
+
self._idle_timeout = idle_timeout
|
|
109
|
+
self._flow_timeout = flow_timeout
|
|
110
|
+
self._save_pcap = save_pcap
|
|
111
|
+
self._pcap_dir = pcap_dir
|
|
112
|
+
self._handle = None
|
|
113
|
+
self._thread: threading.Thread | None = None
|
|
114
|
+
self._session = None
|
|
115
|
+
self._writer = None
|
|
116
|
+
self._callback = None
|
|
117
|
+
|
|
118
|
+
def start(self) -> None:
|
|
119
|
+
errbuf = ctypes.create_string_buffer(256)
|
|
120
|
+
self._handle = pcap_open_live(self._interface, 65535, 1, 1000, errbuf)
|
|
121
|
+
if not self._handle:
|
|
122
|
+
raise RuntimeError(f"pcap_open_live failed: {errbuf.value.decode()}")
|
|
123
|
+
|
|
124
|
+
if self._save_pcap:
|
|
125
|
+
self._writer = _PcapSavingWriter(self._on_flow, self._pcap_dir, self._handle)
|
|
126
|
+
else:
|
|
127
|
+
self._writer = CallbackWriter(self._on_flow)
|
|
128
|
+
|
|
129
|
+
self._session = FlowSession(self._writer, self._flow_timeout)
|
|
130
|
+
self._pkt_counter = 0
|
|
131
|
+
|
|
132
|
+
@PcapHandler_cb
|
|
133
|
+
def _callback(user, pkthdr_p, pkt_data):
|
|
134
|
+
hdr = pkthdr_p.contents
|
|
135
|
+
ts = hdr.ts_sec + hdr.ts_usec / 1_000_000
|
|
136
|
+
buf = ctypes.string_at(pkt_data, hdr.caplen)
|
|
137
|
+
parsed = _parse_packet(buf)
|
|
138
|
+
if parsed is None:
|
|
139
|
+
return
|
|
140
|
+
src_ip, dst_ip, src_port, dst_port, proto, pkt_len, hdr_len, pay_len, flags, win = parsed
|
|
141
|
+
if self._save_pcap:
|
|
142
|
+
fwd_key = (proto, src_ip, dst_ip, src_port, dst_port)
|
|
143
|
+
bwd_key = (proto, dst_ip, src_ip, dst_port, src_port)
|
|
144
|
+
key = fwd_key if (fwd_key in self._session._flows
|
|
145
|
+
or bwd_key not in self._session._flows) else bwd_key
|
|
146
|
+
self._writer.buffer_packet(key, hdr, buf)
|
|
147
|
+
self._session.process(ts, src_ip, dst_ip, src_port, dst_port,
|
|
148
|
+
proto, pkt_len, hdr_len, pay_len, flags, win)
|
|
149
|
+
self._pkt_counter += 1
|
|
150
|
+
if self._pkt_counter % self._GC_INTERVAL == 0:
|
|
151
|
+
self._session.gc(ts)
|
|
152
|
+
|
|
153
|
+
# Keep reference to prevent ctypes GC
|
|
154
|
+
self._callback = _callback
|
|
155
|
+
|
|
156
|
+
self._thread = threading.Thread(
|
|
157
|
+
target=pcap_loop,
|
|
158
|
+
args=(self._handle, -1, self._callback, None),
|
|
159
|
+
daemon=True,
|
|
160
|
+
)
|
|
161
|
+
self._thread.start()
|
|
162
|
+
|
|
163
|
+
def stop(self) -> None:
|
|
164
|
+
if self._handle:
|
|
165
|
+
pcap_breakloop(self._handle)
|
|
166
|
+
if self._thread:
|
|
167
|
+
self._thread.join(timeout=5)
|
|
168
|
+
if self._session:
|
|
169
|
+
self._session.flush_all()
|
|
170
|
+
if self._handle:
|
|
171
|
+
pcap_close(self._handle)
|
|
172
|
+
self._handle = None
|
|
173
|
+
|
|
174
|
+
def __enter__(self):
|
|
175
|
+
return self
|
|
176
|
+
|
|
177
|
+
def __exit__(self, *_):
|
|
178
|
+
self.stop()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def capture_live(
|
|
182
|
+
interface: str,
|
|
183
|
+
on_flow: callable,
|
|
184
|
+
idle_timeout: float = 30.0,
|
|
185
|
+
flow_timeout: float = FLOW_TIMEOUT,
|
|
186
|
+
save_pcap: bool = False,
|
|
187
|
+
pcap_dir: str | None = None,
|
|
188
|
+
) -> CaptureHandle:
|
|
189
|
+
"""
|
|
190
|
+
Capture live packets from a network interface and emit completed flows.
|
|
191
|
+
|
|
192
|
+
Requires root or CAP_NET_RAW privilege.
|
|
193
|
+
|
|
194
|
+
Parameters
|
|
195
|
+
----------
|
|
196
|
+
interface: Network interface name (e.g. "eth0").
|
|
197
|
+
on_flow: Called with a flow dict (82 fields) when a flow completes.
|
|
198
|
+
Same column names as convert_pcap_to_csv output.
|
|
199
|
+
idle_timeout: Seconds of inactivity before a flow is emitted (default 30).
|
|
200
|
+
flow_timeout: Absolute max flow duration in seconds (default 120).
|
|
201
|
+
save_pcap: Save raw packets of each completed flow to a .pcap file.
|
|
202
|
+
pcap_dir: Directory for .pcap files. Required when save_pcap=True.
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
CaptureHandle — call .start() to begin capture, .stop() to end.
|
|
207
|
+
|
|
208
|
+
Example
|
|
209
|
+
-------
|
|
210
|
+
>>> handle = capture_live("eth0", on_flow=lambda f: print(f["flow_duration"]))
|
|
211
|
+
>>> handle.start()
|
|
212
|
+
>>> import time; time.sleep(60)
|
|
213
|
+
>>> handle.stop()
|
|
214
|
+
"""
|
|
215
|
+
return CaptureHandle(
|
|
216
|
+
interface=interface.encode() if isinstance(interface, str) else interface,
|
|
217
|
+
on_flow=on_flow,
|
|
218
|
+
idle_timeout=idle_timeout,
|
|
219
|
+
flow_timeout=flow_timeout,
|
|
220
|
+
save_pcap=save_pcap,
|
|
221
|
+
pcap_dir=pcap_dir,
|
|
222
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
FLOW_TIMEOUT = 120.0 # seconds of inactivity before a flow is flushed
|
|
2
|
+
ACTIVE_TIMEOUT = 5.0 # gap > this separates active from idle periods
|
|
3
|
+
CLUMP_TIMEOUT = 1.0 # gap > this starts a new subflow
|
|
4
|
+
BULK_BOUND = 4 # minimum packets required to register a bulk transfer
|
|
5
|
+
CSV_BUFFER_ROWS = 500 # rows buffered in memory before writing to disk
|
|
6
|
+
|
|
7
|
+
# TCP flag bitmasks (RFC 793 / RFC 3168)
|
|
8
|
+
TCP_FIN = 0x01
|
|
9
|
+
TCP_SYN = 0x02
|
|
10
|
+
TCP_RST = 0x04
|
|
11
|
+
TCP_PSH = 0x08
|
|
12
|
+
TCP_ACK = 0x10
|
|
13
|
+
TCP_URG = 0x20
|
|
14
|
+
TCP_ECE = 0x40
|
|
15
|
+
TCP_CWR = 0x80
|
|
16
|
+
|
|
17
|
+
FORWARD = 0
|
|
18
|
+
BACKWARD = 1
|