140 lines
4.3 KiB
C++
140 lines
4.3 KiB
C++
#define PY_SSIZE_T_CLEAN
|
|
#include <Python.h>
|
|
|
|
#include "pyproxy/settings.cpp"
|
|
#include "pyproxy/pyproxy.cpp"
|
|
#include "classes/netfilter.cpp"
|
|
#include <iostream>
|
|
#include <stdexcept>
|
|
#include <cstdlib>
|
|
#include <endian.h>
|
|
#include "utils.cpp"
|
|
|
|
using namespace std;
|
|
using namespace Firegex::PyProxy;
|
|
using Firegex::NfQueue::MultiThreadQueue;
|
|
|
|
/*
|
|
|
|
How python code is handles:
|
|
|
|
User code example:
|
|
```python
|
|
|
|
from firegex.nfproxy import DROP, ACCEPT, pyfilter
|
|
|
|
@pyfilter
|
|
def invalid_curl_agent(http):
|
|
if "curl" in http.headers.get("User-Agent", ""):
|
|
return DROP
|
|
return ACCEPT
|
|
|
|
```
|
|
|
|
The code is now edited adding an intestation and a end statement:
|
|
```python
|
|
<user_code>
|
|
__firegex_pyfilter_enabled = ["invalid_curl_agent", "func3"] # This list is dynamically generated by firegex backend
|
|
__firegex_proto = "http"
|
|
import firegex.nfproxy.internals
|
|
firegex.nfproxy.internals.compile(globals(), locals()) # This function can save other global variables, to use by the packet handler and is used generally to check and optimize the code
|
|
````
|
|
(First lines are the same to keep line of code consistent on exceptions messages)
|
|
|
|
This code will be executed only once, and is needed to build the global and local context to use
|
|
The globals and locals generated here are copied for each connection, and are used to handle the packets
|
|
|
|
Using C API will be injected in global context the following informations:
|
|
|
|
__firegex_packet_info = {
|
|
"data" = b"raw data found on L4",
|
|
"raw_packet" = b"raw packet",
|
|
"is_input" = True, # If the packet is incoming from a client
|
|
"is_ipv6" = False, # If the packet is ipv6
|
|
"is_tcp" = True, # If the packet is tcp
|
|
}
|
|
|
|
As result the packet handler is responsible to return a dictionary in the global context with the following dictionary:
|
|
__firegex_pyfilter_result = {
|
|
"action": REJECT, # One of PyFilterResponse
|
|
"matched_by": "invalid_curl_agent", # The function that matched the packet (used if action = DROP or REJECT or MANGLE)
|
|
"mangled_packet": b"new packet" # The new packet to send to the kernel (used if action = MANGLE)
|
|
}
|
|
|
|
PyFilterResponse {
|
|
ACCEPT = 0,
|
|
DROP = 1,
|
|
REJECT = 2,
|
|
MANGLE = 3,
|
|
EXCEPTION = 4,
|
|
INVALID = 5
|
|
};
|
|
|
|
Every time a packet is received, the packet handler will execute the following code:
|
|
```python
|
|
firegex.nfproxy.internals.handle_packet()
|
|
````
|
|
|
|
The TCP stream is sorted by libtins using c++ code, but the c++ code is not responsabile di buffer the stream, but only to sort those
|
|
So firegex handle_packet has to implement a way to limit memory usage, this dipends on what methods you choose to use to filter packets
|
|
firegex lib will give you all the needed possibilities to do this is many ways
|
|
|
|
Final note: is not raccomanded to use variables that starts with __firegex_ in your code, because they may break the nfproxy
|
|
*/
|
|
|
|
|
|
|
|
void config_updater (){
|
|
while (true){
|
|
PyThreadState* state = PyEval_SaveThread(); // Release GIL while doing IO operation
|
|
uint32_t code_size;
|
|
memcpy(&code_size, control_socket.recv(4).c_str(), 4);
|
|
code_size = be32toh(code_size);
|
|
string code = control_socket.recv(code_size);
|
|
#ifdef DEBUG
|
|
cerr << "[DEBUG] [updater] Received code: " << code << endl;
|
|
#endif
|
|
cerr << "[info] [updater] Updating configuration" << endl;
|
|
PyEval_AcquireThread(state); //Restore GIL before executing python code
|
|
try{
|
|
config.reset(new PyCodeConfig(code));
|
|
cerr << "[info] [updater] Config update done" << endl;
|
|
control_socket << "ACK OK" << endl;
|
|
}catch(const std::exception& e){
|
|
cerr << "[error] [updater] Failed to build new configuration!" << endl;
|
|
control_socket << "ACK FAIL " << e.what() << endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
|
// Connect to the python backend using the unix socket
|
|
init_control_socket();
|
|
|
|
// Initialize the python interpreter
|
|
Py_Initialize();
|
|
atexit(Py_Finalize);
|
|
init_handle_packet_code(); //Compile the static code used to handle packets
|
|
|
|
int n_of_threads = 1;
|
|
char * n_threads_str = getenv("NTHREADS");
|
|
if (n_threads_str != nullptr) n_of_threads = ::atoi(n_threads_str);
|
|
if(n_of_threads <= 0) n_of_threads = 1;
|
|
|
|
config.reset(new PyCodeConfig());
|
|
|
|
MultiThreadQueue<PyProxyQueue> queue(n_of_threads);
|
|
|
|
control_socket << "QUEUE " << queue.queue_num() << endl;
|
|
|
|
cerr << "[info] [main] Queue: " << queue.queue_num() << " threads assigned: " << n_of_threads << endl;
|
|
|
|
thread qthr([&](){
|
|
queue.start();
|
|
});
|
|
config_updater();
|
|
qthr.join();
|
|
}
|