firegex 2.5.3__tar.gz → 3.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.
- firegex-3.0.0/PKG-INFO +170 -0
- firegex-3.0.0/README.md +142 -0
- {firegex-2.5.3 → firegex-3.0.0}/fgex +0 -1
- firegex-3.0.0/firegex/__init__.py +5 -0
- {firegex-2.5.3 → firegex-3.0.0}/firegex/__main__.py +0 -1
- firegex-3.0.0/firegex/cli.py +76 -0
- firegex-3.0.0/firegex/nfproxy/__init__.py +39 -0
- firegex-3.0.0/firegex/nfproxy/internals/__init__.py +160 -0
- firegex-3.0.0/firegex/nfproxy/internals/data.py +149 -0
- firegex-3.0.0/firegex/nfproxy/internals/exceptions.py +15 -0
- firegex-3.0.0/firegex/nfproxy/internals/models.py +44 -0
- firegex-3.0.0/firegex/nfproxy/models/__init__.py +31 -0
- firegex-3.0.0/firegex/nfproxy/models/http.py +357 -0
- firegex-3.0.0/firegex/nfproxy/models/tcp.py +87 -0
- firegex-3.0.0/firegex/nfproxy/proxysim/__init__.py +303 -0
- firegex-3.0.0/firegex.egg-info/PKG-INFO +170 -0
- firegex-3.0.0/firegex.egg-info/SOURCES.txt +22 -0
- firegex-3.0.0/firegex.egg-info/requires.txt +6 -0
- firegex-3.0.0/requirements.txt +6 -0
- {firegex-2.5.3 → firegex-3.0.0}/setup.py +1 -1
- firegex-2.5.3/PKG-INFO +0 -37
- firegex-2.5.3/README.md +0 -3
- firegex-2.5.3/firegex/__init__.py +0 -7
- firegex-2.5.3/firegex.egg-info/PKG-INFO +0 -37
- firegex-2.5.3/firegex.egg-info/SOURCES.txt +0 -12
- firegex-2.5.3/firegex.egg-info/requires.txt +0 -12
- firegex-2.5.3/requirements.txt +0 -14
- {firegex-2.5.3 → firegex-3.0.0}/MANIFEST.in +0 -0
- {firegex-2.5.3 → firegex-3.0.0}/firegex.egg-info/dependency_links.txt +0 -0
- {firegex-2.5.3 → firegex-3.0.0}/firegex.egg-info/top_level.txt +0 -0
- {firegex-2.5.3 → firegex-3.0.0}/setup.cfg +0 -0
firegex-3.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: firegex
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Firegex client
|
|
5
|
+
Home-page: https://github.com/pwnzer0tt1/firegex
|
|
6
|
+
Author: Pwnzer0tt1
|
|
7
|
+
Author-email: pwnzer0tt1@poliba.it
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: typer==0.15.2
|
|
14
|
+
Requires-Dist: pydantic>=2
|
|
15
|
+
Requires-Dist: typing-extensions>=4.7.1
|
|
16
|
+
Requires-Dist: watchfiles
|
|
17
|
+
Requires-Dist: fgex
|
|
18
|
+
Requires-Dist: pyllhttp
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: requires-dist
|
|
26
|
+
Dynamic: requires-python
|
|
27
|
+
Dynamic: summary
|
|
28
|
+
|
|
29
|
+
# Firegex Python Library and CLI
|
|
30
|
+
|
|
31
|
+
This is the Python library for Firegex. It is used to get additional features of Firegex and use the feature of the command `fgex`.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install -U firegex
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
fgex is an alias of firegex. You can use `fgex` instead of `firegex`.
|
|
40
|
+
|
|
41
|
+
## Command line usage:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
➤ fgex nfproxy -h
|
|
45
|
+
|
|
46
|
+
Usage: fgex nfproxy [OPTIONS] FILTER_FILE ADDRESS PORT
|
|
47
|
+
|
|
48
|
+
Run an nfproxy simulation
|
|
49
|
+
|
|
50
|
+
╭─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
51
|
+
│ * filter_file TEXT The path to the filter file [default: None] [required] │
|
|
52
|
+
│ * address TEXT The address of the target to proxy [default: None] [required] │
|
|
53
|
+
│ * port INTEGER The port of the target to proxy [default: None] [required] │
|
|
54
|
+
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
55
|
+
╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
56
|
+
│ --proto [tcp|http] The protocol to proxy [default: tcp] │
|
|
57
|
+
│ --from-address TEXT The address of the local server [default: None] │
|
|
58
|
+
│ --from-port INTEGER The port of the local server [default: 7474] │
|
|
59
|
+
│ -6 Use IPv6 for the connection │
|
|
60
|
+
│ --help -h Show this message and exit. │
|
|
61
|
+
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Library usage:
|
|
65
|
+
|
|
66
|
+
## NfProxy decorator
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from firegex.nfproxy import pyfilter
|
|
70
|
+
```
|
|
71
|
+
This decorator is used to create a filter for the nfproxy.
|
|
72
|
+
Example:
|
|
73
|
+
```python
|
|
74
|
+
@pyfilter
|
|
75
|
+
def my_filter(raw_packet: RawPacket): #Logging filter
|
|
76
|
+
print(raw_packet.data)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Data handlers
|
|
80
|
+
|
|
81
|
+
### RawPacket
|
|
82
|
+
```python
|
|
83
|
+
from firegex.nfproxy import RawPacket
|
|
84
|
+
```
|
|
85
|
+
This handler will be called every time arrives a packet from the network. It will receive a RawPacket object with the following properties:
|
|
86
|
+
- is_input: bool - It's true if the packet is an input packet, false if it's an output packet
|
|
87
|
+
- is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
|
|
88
|
+
- is_tcp: bool - It's true if the packet is a tcp packet, false if it's an udp packet
|
|
89
|
+
- data: bytes - The data of the packet assembled and sorted from TCP
|
|
90
|
+
- l4_size: int - The size of the layer 4 data
|
|
91
|
+
- raw_packet_header_len: int - The size of the original packet header
|
|
92
|
+
- l4_data: bytes - The layer 4 payload of the packet
|
|
93
|
+
- raw_packet: bytes - The raw packet with IP and TCP headers
|
|
94
|
+
|
|
95
|
+
### TCPInputStream
|
|
96
|
+
Alias: TCPClientStream
|
|
97
|
+
```python
|
|
98
|
+
from firegex.nfproxy import TCPInputStream
|
|
99
|
+
```
|
|
100
|
+
This handler will be called every time a TCP stream is assembled in input. It will receive a TCPInputStream object with the following properties:
|
|
101
|
+
- data: bytes - The data of the packets assembled and sorted from TCP
|
|
102
|
+
- is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
|
|
103
|
+
- total_stream_size: int - The size of the stream
|
|
104
|
+
|
|
105
|
+
### TCPOutputStream
|
|
106
|
+
Alias: TCPServerStream
|
|
107
|
+
```python
|
|
108
|
+
from firegex.nfproxy import TCPOutputStream
|
|
109
|
+
```
|
|
110
|
+
This handler will be called every time a TCP stream is assembled in output. It will receive a TCPOutputStream object with the following properties:
|
|
111
|
+
- data: bytes - The data of the packets assembled and sorted from TCP
|
|
112
|
+
- is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
|
|
113
|
+
- total_stream_size: int - The size of the stream
|
|
114
|
+
|
|
115
|
+
### HttpRequest
|
|
116
|
+
```python
|
|
117
|
+
from firegex.nfproxy import HttpRequest
|
|
118
|
+
```
|
|
119
|
+
This handler will be called twice: one for the request headers and one for the request body. It will receive a HttpRequest object with the following properties:
|
|
120
|
+
- method: bytes - The method of the request
|
|
121
|
+
- url: str - The url of the request
|
|
122
|
+
- headers: dict - The headers of the request
|
|
123
|
+
- user_agent: str - The user agent of the request
|
|
124
|
+
- content_encoding: str - The content encoding of the request
|
|
125
|
+
- has_begun: bool - It's true if the request has begun
|
|
126
|
+
- body: bytes - The body of the request
|
|
127
|
+
- headers_complete: bool - It's true if the headers are complete
|
|
128
|
+
- message_complete: bool - It's true if the message is complete
|
|
129
|
+
- http_version: str - The http version of the request
|
|
130
|
+
- keep_alive: bool - It's true if the request should keep alive
|
|
131
|
+
- should_upgrade: bool - It's true if the request should upgrade
|
|
132
|
+
- content_length: int - The content length of the request
|
|
133
|
+
- get_header(header: str, default=None): str - Get a header from the request without caring about the case
|
|
134
|
+
- total_size: int - The total size of the stream
|
|
135
|
+
- stream: bytes - The stream of the request
|
|
136
|
+
|
|
137
|
+
### HttpRequestHeader
|
|
138
|
+
```python
|
|
139
|
+
from firegex.nfproxy import HttpRequestHeader
|
|
140
|
+
```
|
|
141
|
+
This handler will be called only when the request headers are complete. It will receive a HttpRequestHeader object with the same properties as HttpRequest.
|
|
142
|
+
|
|
143
|
+
### HttpResponse
|
|
144
|
+
```python
|
|
145
|
+
from firegex.nfproxy import HttpResponse
|
|
146
|
+
```
|
|
147
|
+
This handler will be called twice: one for the response headers and one for the response body. It will receive a HttpResponse object with the following properties:
|
|
148
|
+
- status_code: int - The status code of the response
|
|
149
|
+
- url: str - The url of the response
|
|
150
|
+
- headers: dict - The headers of the response
|
|
151
|
+
- user_agent: str - The user agent of the response
|
|
152
|
+
- content_encoding: str - The content encoding of the response
|
|
153
|
+
- has_begun: bool - It's true if the response has begun
|
|
154
|
+
- body: bytes - The body of the response
|
|
155
|
+
- headers_complete: bool - It's true if the headers are complete
|
|
156
|
+
- message_complete: bool - It's true if the message is complete
|
|
157
|
+
- http_version: str - The http version of the response
|
|
158
|
+
- keep_alive: bool - It's true if the response should keep alive
|
|
159
|
+
- should_upgrade: bool - It's true if the response should upgrade
|
|
160
|
+
- content_length: int - The content length of the response
|
|
161
|
+
- get_header(header: str, default=None): str - Get a header from the response without caring about the case
|
|
162
|
+
- total_size: int - The total size of the stream
|
|
163
|
+
- stream: bytes - The stream of the response
|
|
164
|
+
|
|
165
|
+
### HttpResponseHeader
|
|
166
|
+
```python
|
|
167
|
+
from firegex.nfproxy import HttpResponseHeader
|
|
168
|
+
```
|
|
169
|
+
This handler will be called only when the response headers are complete. It will receive a HttpResponseHeader object with the same properties as HttpResponse.
|
|
170
|
+
|
firegex-3.0.0/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Firegex Python Library and CLI
|
|
2
|
+
|
|
3
|
+
This is the Python library for Firegex. It is used to get additional features of Firegex and use the feature of the command `fgex`.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install -U firegex
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
fgex is an alias of firegex. You can use `fgex` instead of `firegex`.
|
|
12
|
+
|
|
13
|
+
## Command line usage:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
➤ fgex nfproxy -h
|
|
17
|
+
|
|
18
|
+
Usage: fgex nfproxy [OPTIONS] FILTER_FILE ADDRESS PORT
|
|
19
|
+
|
|
20
|
+
Run an nfproxy simulation
|
|
21
|
+
|
|
22
|
+
╭─ Arguments ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
23
|
+
│ * filter_file TEXT The path to the filter file [default: None] [required] │
|
|
24
|
+
│ * address TEXT The address of the target to proxy [default: None] [required] │
|
|
25
|
+
│ * port INTEGER The port of the target to proxy [default: None] [required] │
|
|
26
|
+
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
27
|
+
╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
28
|
+
│ --proto [tcp|http] The protocol to proxy [default: tcp] │
|
|
29
|
+
│ --from-address TEXT The address of the local server [default: None] │
|
|
30
|
+
│ --from-port INTEGER The port of the local server [default: 7474] │
|
|
31
|
+
│ -6 Use IPv6 for the connection │
|
|
32
|
+
│ --help -h Show this message and exit. │
|
|
33
|
+
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Library usage:
|
|
37
|
+
|
|
38
|
+
## NfProxy decorator
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from firegex.nfproxy import pyfilter
|
|
42
|
+
```
|
|
43
|
+
This decorator is used to create a filter for the nfproxy.
|
|
44
|
+
Example:
|
|
45
|
+
```python
|
|
46
|
+
@pyfilter
|
|
47
|
+
def my_filter(raw_packet: RawPacket): #Logging filter
|
|
48
|
+
print(raw_packet.data)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Data handlers
|
|
52
|
+
|
|
53
|
+
### RawPacket
|
|
54
|
+
```python
|
|
55
|
+
from firegex.nfproxy import RawPacket
|
|
56
|
+
```
|
|
57
|
+
This handler will be called every time arrives a packet from the network. It will receive a RawPacket object with the following properties:
|
|
58
|
+
- is_input: bool - It's true if the packet is an input packet, false if it's an output packet
|
|
59
|
+
- is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
|
|
60
|
+
- is_tcp: bool - It's true if the packet is a tcp packet, false if it's an udp packet
|
|
61
|
+
- data: bytes - The data of the packet assembled and sorted from TCP
|
|
62
|
+
- l4_size: int - The size of the layer 4 data
|
|
63
|
+
- raw_packet_header_len: int - The size of the original packet header
|
|
64
|
+
- l4_data: bytes - The layer 4 payload of the packet
|
|
65
|
+
- raw_packet: bytes - The raw packet with IP and TCP headers
|
|
66
|
+
|
|
67
|
+
### TCPInputStream
|
|
68
|
+
Alias: TCPClientStream
|
|
69
|
+
```python
|
|
70
|
+
from firegex.nfproxy import TCPInputStream
|
|
71
|
+
```
|
|
72
|
+
This handler will be called every time a TCP stream is assembled in input. It will receive a TCPInputStream object with the following properties:
|
|
73
|
+
- data: bytes - The data of the packets assembled and sorted from TCP
|
|
74
|
+
- is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
|
|
75
|
+
- total_stream_size: int - The size of the stream
|
|
76
|
+
|
|
77
|
+
### TCPOutputStream
|
|
78
|
+
Alias: TCPServerStream
|
|
79
|
+
```python
|
|
80
|
+
from firegex.nfproxy import TCPOutputStream
|
|
81
|
+
```
|
|
82
|
+
This handler will be called every time a TCP stream is assembled in output. It will receive a TCPOutputStream object with the following properties:
|
|
83
|
+
- data: bytes - The data of the packets assembled and sorted from TCP
|
|
84
|
+
- is_ipv6: bool - It's true if the packet is an ipv6 packet, false if it's an ipv4 packet
|
|
85
|
+
- total_stream_size: int - The size of the stream
|
|
86
|
+
|
|
87
|
+
### HttpRequest
|
|
88
|
+
```python
|
|
89
|
+
from firegex.nfproxy import HttpRequest
|
|
90
|
+
```
|
|
91
|
+
This handler will be called twice: one for the request headers and one for the request body. It will receive a HttpRequest object with the following properties:
|
|
92
|
+
- method: bytes - The method of the request
|
|
93
|
+
- url: str - The url of the request
|
|
94
|
+
- headers: dict - The headers of the request
|
|
95
|
+
- user_agent: str - The user agent of the request
|
|
96
|
+
- content_encoding: str - The content encoding of the request
|
|
97
|
+
- has_begun: bool - It's true if the request has begun
|
|
98
|
+
- body: bytes - The body of the request
|
|
99
|
+
- headers_complete: bool - It's true if the headers are complete
|
|
100
|
+
- message_complete: bool - It's true if the message is complete
|
|
101
|
+
- http_version: str - The http version of the request
|
|
102
|
+
- keep_alive: bool - It's true if the request should keep alive
|
|
103
|
+
- should_upgrade: bool - It's true if the request should upgrade
|
|
104
|
+
- content_length: int - The content length of the request
|
|
105
|
+
- get_header(header: str, default=None): str - Get a header from the request without caring about the case
|
|
106
|
+
- total_size: int - The total size of the stream
|
|
107
|
+
- stream: bytes - The stream of the request
|
|
108
|
+
|
|
109
|
+
### HttpRequestHeader
|
|
110
|
+
```python
|
|
111
|
+
from firegex.nfproxy import HttpRequestHeader
|
|
112
|
+
```
|
|
113
|
+
This handler will be called only when the request headers are complete. It will receive a HttpRequestHeader object with the same properties as HttpRequest.
|
|
114
|
+
|
|
115
|
+
### HttpResponse
|
|
116
|
+
```python
|
|
117
|
+
from firegex.nfproxy import HttpResponse
|
|
118
|
+
```
|
|
119
|
+
This handler will be called twice: one for the response headers and one for the response body. It will receive a HttpResponse object with the following properties:
|
|
120
|
+
- status_code: int - The status code of the response
|
|
121
|
+
- url: str - The url of the response
|
|
122
|
+
- headers: dict - The headers of the response
|
|
123
|
+
- user_agent: str - The user agent of the response
|
|
124
|
+
- content_encoding: str - The content encoding of the response
|
|
125
|
+
- has_begun: bool - It's true if the response has begun
|
|
126
|
+
- body: bytes - The body of the response
|
|
127
|
+
- headers_complete: bool - It's true if the headers are complete
|
|
128
|
+
- message_complete: bool - It's true if the message is complete
|
|
129
|
+
- http_version: str - The http version of the response
|
|
130
|
+
- keep_alive: bool - It's true if the response should keep alive
|
|
131
|
+
- should_upgrade: bool - It's true if the response should upgrade
|
|
132
|
+
- content_length: int - The content length of the response
|
|
133
|
+
- get_header(header: str, default=None): str - Get a header from the response without caring about the case
|
|
134
|
+
- total_size: int - The total size of the stream
|
|
135
|
+
- stream: bytes - The stream of the response
|
|
136
|
+
|
|
137
|
+
### HttpResponseHeader
|
|
138
|
+
```python
|
|
139
|
+
from firegex.nfproxy import HttpResponseHeader
|
|
140
|
+
```
|
|
141
|
+
This handler will be called only when the response headers are complete. It will receive a HttpResponseHeader object with the same properties as HttpResponse.
|
|
142
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
|
|
2
|
+
#!/usr/bin/env python3
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from rich import print
|
|
6
|
+
from rich.markup import escape
|
|
7
|
+
from typer import Exit
|
|
8
|
+
from firegex import __version__
|
|
9
|
+
from firegex.nfproxy.proxysim import run_proxy_simulation
|
|
10
|
+
from firegex.nfproxy.models import Protocols
|
|
11
|
+
import os
|
|
12
|
+
import socket
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(
|
|
15
|
+
no_args_is_help=True,
|
|
16
|
+
context_settings={"help_option_names": ["-h", "--help"]}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def close_cli(code:int=1):
|
|
20
|
+
raise Exit(code)
|
|
21
|
+
|
|
22
|
+
DEV_MODE = __version__ == "0.0.0"
|
|
23
|
+
|
|
24
|
+
def test_connection(host, port, use_ipv6=False):
|
|
25
|
+
family = socket.AF_INET6 if use_ipv6 else socket.AF_INET
|
|
26
|
+
sock = socket.socket(family, socket.SOCK_STREAM)
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
sock.settimeout(3)
|
|
30
|
+
sock.connect((host, port))
|
|
31
|
+
return True
|
|
32
|
+
except Exception:
|
|
33
|
+
return False
|
|
34
|
+
finally:
|
|
35
|
+
sock.close()
|
|
36
|
+
|
|
37
|
+
@app.command(help="Run an nfproxy simulation")
|
|
38
|
+
def nfproxy(
|
|
39
|
+
filter_file: str = typer.Argument(..., help="The path to the filter file"),
|
|
40
|
+
address: str = typer.Argument(..., help="The address of the target to proxy"),
|
|
41
|
+
port: int = typer.Argument(..., help="The port of the target to proxy"),
|
|
42
|
+
|
|
43
|
+
proto: Protocols = typer.Option(Protocols.TCP.value, help="The protocol to proxy"),
|
|
44
|
+
from_address: str = typer.Option(None, help="The address of the local server"),
|
|
45
|
+
from_port: int = typer.Option(7474, help="The port of the local server"),
|
|
46
|
+
ipv6: bool = typer.Option(False, "-6", help="Use IPv6 for the connection"),
|
|
47
|
+
):
|
|
48
|
+
if from_address is None:
|
|
49
|
+
from_address = "::1" if ipv6 else "127.0.0.1"
|
|
50
|
+
if not os.path.isfile(filter_file):
|
|
51
|
+
print(f"[bold red]'{escape(os.path.abspath(filter_file))}' not found[/]")
|
|
52
|
+
close_cli()
|
|
53
|
+
if not test_connection(address, port, ipv6):
|
|
54
|
+
print(f"[bold red]Can't connect to {escape(address)}:{port}[/]")
|
|
55
|
+
close_cli()
|
|
56
|
+
run_proxy_simulation(filter_file, proto.value, address, port, from_address, from_port, ipv6)
|
|
57
|
+
|
|
58
|
+
def version_callback(verison: bool):
|
|
59
|
+
if verison:
|
|
60
|
+
print(__version__, "Development Mode" if DEV_MODE else "Release")
|
|
61
|
+
raise typer.Exit()
|
|
62
|
+
|
|
63
|
+
@app.callback()
|
|
64
|
+
def main(
|
|
65
|
+
verison: bool = typer.Option(False, "--version", "-v", help="Show the version of the client", callback=version_callback),
|
|
66
|
+
):
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
def run():
|
|
70
|
+
try:
|
|
71
|
+
app()
|
|
72
|
+
except KeyboardInterrupt:
|
|
73
|
+
print("[bold yellow]Operation cancelled[/]")
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
run()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
from firegex.nfproxy.models import RawPacket, TCPInputStream, TCPOutputStream, TCPClientStream, TCPServerStream
|
|
3
|
+
from firegex.nfproxy.internals.models import Action, FullStreamAction
|
|
4
|
+
|
|
5
|
+
ACCEPT = Action.ACCEPT
|
|
6
|
+
DROP = Action.DROP
|
|
7
|
+
REJECT = Action.REJECT
|
|
8
|
+
UNSTABLE_MANGLE = Action.MANGLE
|
|
9
|
+
|
|
10
|
+
def pyfilter(func):
|
|
11
|
+
"""
|
|
12
|
+
Decorator to mark functions that will be used in the proxy.
|
|
13
|
+
Stores the function reference in a global registry.
|
|
14
|
+
"""
|
|
15
|
+
if not hasattr(pyfilter, "registry"):
|
|
16
|
+
pyfilter.registry = set()
|
|
17
|
+
|
|
18
|
+
pyfilter.registry.add(func.__name__)
|
|
19
|
+
|
|
20
|
+
@functools.wraps(func)
|
|
21
|
+
def wrapper(*args, **kwargs):
|
|
22
|
+
return func(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
return wrapper
|
|
25
|
+
|
|
26
|
+
def get_pyfilters():
|
|
27
|
+
"""Returns the list of functions marked with @pyfilter."""
|
|
28
|
+
return list(pyfilter.registry)
|
|
29
|
+
|
|
30
|
+
def clear_pyfilter_registry():
|
|
31
|
+
"""Clears the pyfilter registry."""
|
|
32
|
+
if hasattr(pyfilter, "registry"):
|
|
33
|
+
pyfilter.registry.clear()
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
"ACCEPT", "DROP", "REJECT", "UNSTABLE_MANGLE"
|
|
37
|
+
"Action", "FullStreamAction", "pyfilter",
|
|
38
|
+
"RawPacket", "TCPInputStream", "TCPOutputStream", "TCPClientStream", "TCPServerStream"
|
|
39
|
+
]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from inspect import signature
|
|
2
|
+
from firegex.nfproxy.internals.models import Action, FullStreamAction
|
|
3
|
+
from firegex.nfproxy.internals.models import FilterHandler, PacketHandlerResult
|
|
4
|
+
import functools
|
|
5
|
+
from firegex.nfproxy.internals.data import DataStreamCtx
|
|
6
|
+
from firegex.nfproxy.internals.exceptions import NotReadyToRun, StreamFullReject, DropPacket, RejectConnection, StreamFullDrop
|
|
7
|
+
from firegex.nfproxy.internals.data import RawPacket
|
|
8
|
+
|
|
9
|
+
def context_call(glob, func, *args, **kargs):
|
|
10
|
+
glob["__firegex_tmp_args"] = args
|
|
11
|
+
glob["__firegex_tmp_kargs"] = kargs
|
|
12
|
+
glob["__firege_tmp_call"] = func
|
|
13
|
+
res = eval("__firege_tmp_call(*__firegex_tmp_args, **__firegex_tmp_kargs)", glob, glob)
|
|
14
|
+
if "__firegex_tmp_args" in glob.keys():
|
|
15
|
+
del glob["__firegex_tmp_args"]
|
|
16
|
+
if "__firegex_tmp_kargs" in glob.keys():
|
|
17
|
+
del glob["__firegex_tmp_kargs"]
|
|
18
|
+
if "__firege_tmp_call" in glob.keys():
|
|
19
|
+
del glob["__firege_tmp_call"]
|
|
20
|
+
return res
|
|
21
|
+
|
|
22
|
+
def generate_filter_structure(filters: list[str], proto:str, glob:dict) -> list[FilterHandler]:
|
|
23
|
+
from firegex.nfproxy.models import type_annotations_associations
|
|
24
|
+
if proto not in type_annotations_associations.keys():
|
|
25
|
+
raise Exception("Invalid protocol")
|
|
26
|
+
res = []
|
|
27
|
+
valid_annotation_type = type_annotations_associations[proto]
|
|
28
|
+
def add_func_to_list(func):
|
|
29
|
+
if not callable(func):
|
|
30
|
+
raise Exception(f"{func} is not a function")
|
|
31
|
+
sig = signature(func)
|
|
32
|
+
params_function = {}
|
|
33
|
+
|
|
34
|
+
for k, v in sig.parameters.items():
|
|
35
|
+
if v.annotation in valid_annotation_type.keys():
|
|
36
|
+
params_function[v.annotation] = valid_annotation_type[v.annotation]
|
|
37
|
+
else:
|
|
38
|
+
raise Exception(f"Invalid type annotation {v.annotation} for function {func.__name__}")
|
|
39
|
+
|
|
40
|
+
res.append(
|
|
41
|
+
FilterHandler(
|
|
42
|
+
func=func,
|
|
43
|
+
name=func.__name__,
|
|
44
|
+
params=params_function,
|
|
45
|
+
proto=proto
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
for filter in filters:
|
|
50
|
+
if not isinstance(filter, str):
|
|
51
|
+
raise Exception("Invalid filter list: must be a list of strings")
|
|
52
|
+
if filter in glob.keys():
|
|
53
|
+
add_func_to_list(glob[filter])
|
|
54
|
+
else:
|
|
55
|
+
raise Exception(f"Filter {filter} not found")
|
|
56
|
+
return res
|
|
57
|
+
|
|
58
|
+
def get_filters_info(code:str, proto:str) -> list[FilterHandler]:
|
|
59
|
+
glob = {}
|
|
60
|
+
exec("import firegex.nfproxy", glob, glob)
|
|
61
|
+
exec("firegex.nfproxy.clear_pyfilter_registry()", glob, glob)
|
|
62
|
+
exec(code, glob, glob)
|
|
63
|
+
filters = eval("firegex.nfproxy.get_pyfilters()", glob, glob)
|
|
64
|
+
try:
|
|
65
|
+
return generate_filter_structure(filters, proto, glob)
|
|
66
|
+
finally:
|
|
67
|
+
exec("firegex.nfproxy.clear_pyfilter_registry()", glob, glob)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_filter_names(code:str, proto:str) -> list[str]:
|
|
71
|
+
return [ele.name for ele in get_filters_info(code, proto)]
|
|
72
|
+
|
|
73
|
+
def handle_packet(glob: dict) -> None:
|
|
74
|
+
internal_data = DataStreamCtx(glob)
|
|
75
|
+
|
|
76
|
+
cache_call = {} # Cache of the data handler calls
|
|
77
|
+
cache_call[RawPacket] = internal_data.current_pkt
|
|
78
|
+
|
|
79
|
+
final_result = Action.ACCEPT
|
|
80
|
+
result = PacketHandlerResult(glob)
|
|
81
|
+
|
|
82
|
+
func_name = None
|
|
83
|
+
mangled_packet = None
|
|
84
|
+
for filter in internal_data.filter_call_info:
|
|
85
|
+
final_params = []
|
|
86
|
+
skip_call = False
|
|
87
|
+
for data_type, data_func in filter.params.items():
|
|
88
|
+
if data_type not in cache_call.keys():
|
|
89
|
+
try:
|
|
90
|
+
cache_call[data_type] = data_func(internal_data)
|
|
91
|
+
except NotReadyToRun:
|
|
92
|
+
cache_call[data_type] = None
|
|
93
|
+
skip_call = True
|
|
94
|
+
break
|
|
95
|
+
except StreamFullDrop:
|
|
96
|
+
result.action = Action.DROP
|
|
97
|
+
result.matched_by = "@MAX_STREAM_SIZE_REACHED"
|
|
98
|
+
return result.set_result()
|
|
99
|
+
except StreamFullReject:
|
|
100
|
+
result.action = Action.REJECT
|
|
101
|
+
result.matched_by = "@MAX_STREAM_SIZE_REACHED"
|
|
102
|
+
return result.set_result()
|
|
103
|
+
except DropPacket:
|
|
104
|
+
result.action = Action.DROP
|
|
105
|
+
result.matched_by = filter.name
|
|
106
|
+
return result.set_result()
|
|
107
|
+
except RejectConnection:
|
|
108
|
+
result.action = Action.REJECT
|
|
109
|
+
result.matched_by = filter.name
|
|
110
|
+
return result.set_result()
|
|
111
|
+
if cache_call[data_type] is None:
|
|
112
|
+
skip_call = True
|
|
113
|
+
break
|
|
114
|
+
final_params.append(cache_call[data_type])
|
|
115
|
+
|
|
116
|
+
if skip_call:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
res = context_call(glob, filter.func, *final_params)
|
|
120
|
+
|
|
121
|
+
if res is None:
|
|
122
|
+
continue #ACCEPTED
|
|
123
|
+
if not isinstance(res, Action):
|
|
124
|
+
raise Exception(f"Invalid return type {type(res)} for function {filter.name}")
|
|
125
|
+
if res == Action.MANGLE:
|
|
126
|
+
mangled_packet = internal_data.current_pkt.raw_packet
|
|
127
|
+
if res != Action.ACCEPT:
|
|
128
|
+
func_name = filter.name
|
|
129
|
+
final_result = res
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
result.action = final_result
|
|
133
|
+
result.matched_by = func_name
|
|
134
|
+
result.mangled_packet = mangled_packet
|
|
135
|
+
|
|
136
|
+
return result.set_result()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def compile(glob:dict) -> None:
|
|
140
|
+
internal_data = DataStreamCtx(glob, init_pkt=False)
|
|
141
|
+
|
|
142
|
+
glob["print"] = functools.partial(print, flush = True)
|
|
143
|
+
|
|
144
|
+
filters = glob["__firegex_pyfilter_enabled"]
|
|
145
|
+
proto = glob["__firegex_proto"]
|
|
146
|
+
|
|
147
|
+
internal_data.filter_call_info = generate_filter_structure(filters, proto, glob)
|
|
148
|
+
|
|
149
|
+
if "FGEX_STREAM_MAX_SIZE" in glob and int(glob["FGEX_STREAM_MAX_SIZE"]) > 0:
|
|
150
|
+
internal_data.stream_max_size = int(glob["FGEX_STREAM_MAX_SIZE"])
|
|
151
|
+
else:
|
|
152
|
+
internal_data.stream_max_size = 1*8e20 # 1MB default value
|
|
153
|
+
|
|
154
|
+
if "FGEX_FULL_STREAM_ACTION" in glob and isinstance(glob["FGEX_FULL_STREAM_ACTION"], FullStreamAction):
|
|
155
|
+
internal_data.full_stream_action = glob["FGEX_FULL_STREAM_ACTION"]
|
|
156
|
+
else:
|
|
157
|
+
internal_data.full_stream_action = FullStreamAction.FLUSH
|
|
158
|
+
|
|
159
|
+
PacketHandlerResult(glob).reset_result()
|
|
160
|
+
|