regex checked by hyperscan directly with error messages
This commit is contained in:
@@ -50,6 +50,21 @@ void config_updater (){
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]){
|
int main(int argc, char *argv[]){
|
||||||
|
|
||||||
|
char * test_regex = getenv("FIREGEX_TEST_REGEX");
|
||||||
|
if (test_regex != nullptr){
|
||||||
|
cerr << "[info] [main] Testing regex: " << test_regex << endl;
|
||||||
|
try{
|
||||||
|
RegexRules::compile_regex(test_regex);
|
||||||
|
cerr << "[info] [main] Test passed" << endl;
|
||||||
|
return 0;
|
||||||
|
}catch(const std::exception& e){
|
||||||
|
cerr << "[error] [updater] Test failed" << endl;
|
||||||
|
cout << e.what() << flush;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int n_of_threads = 1;
|
int n_of_threads = 1;
|
||||||
char * n_threads_str = getenv("NTHREADS");
|
char * n_threads_str = getenv("NTHREADS");
|
||||||
if (n_threads_str != nullptr) n_of_threads = ::atoi(n_threads_str);
|
if (n_threads_str != nullptr) n_of_threads = ::atoi(n_threads_str);
|
||||||
|
|||||||
@@ -59,6 +59,26 @@ class RegexRules{
|
|||||||
public:
|
public:
|
||||||
regex_ruleset output_ruleset, input_ruleset;
|
regex_ruleset output_ruleset, input_ruleset;
|
||||||
|
|
||||||
|
static void compile_regex(char* regex){
|
||||||
|
hs_database_t* db = nullptr;
|
||||||
|
hs_compile_error_t *compile_err = nullptr;
|
||||||
|
if (
|
||||||
|
hs_compile(
|
||||||
|
regex,
|
||||||
|
HS_FLAG_SINGLEMATCH | HS_FLAG_ALLOWEMPTY,
|
||||||
|
HS_MODE_BLOCK,
|
||||||
|
nullptr, &db, &compile_err
|
||||||
|
) != HS_SUCCESS
|
||||||
|
) {
|
||||||
|
string err = string(compile_err->message);
|
||||||
|
hs_free_compile_error(compile_err);
|
||||||
|
throw runtime_error(err);
|
||||||
|
}else{
|
||||||
|
hs_free_database(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static inline u_int16_t glob_seq = 0;
|
static inline u_int16_t glob_seq = 0;
|
||||||
u_int16_t version;
|
u_int16_t version;
|
||||||
@@ -77,6 +97,8 @@ class RegexRules{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void fill_ruleset(vector<pair<string, decoded_regex>> & decoded, regex_ruleset & ruleset){
|
void fill_ruleset(vector<pair<string, decoded_regex>> & decoded, regex_ruleset & ruleset){
|
||||||
size_t n_of_regex = decoded.size();
|
size_t n_of_regex = decoded.size();
|
||||||
if (n_of_regex == 0){
|
if (n_of_regex == 0){
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from modules.nfregex.nftables import FiregexTables
|
from modules.nfregex.nftables import FiregexTables
|
||||||
from utils import run_func
|
from utils import run_func
|
||||||
from modules.nfregex.models import Service, Regex
|
from modules.nfregex.models import Service, Regex
|
||||||
import re
|
|
||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
import traceback
|
import traceback
|
||||||
@@ -10,6 +9,20 @@ from fastapi import HTTPException
|
|||||||
|
|
||||||
nft = FiregexTables()
|
nft = FiregexTables()
|
||||||
|
|
||||||
|
async def test_regex_validity(regex: str) -> bool:
|
||||||
|
proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"../cppregex")
|
||||||
|
process = await asyncio.create_subprocess_exec(
|
||||||
|
proxy_binary_path,
|
||||||
|
stdout=asyncio.subprocess.PIPE,
|
||||||
|
stdin=asyncio.subprocess.DEVNULL,
|
||||||
|
env={"FIREGEX_TEST_REGEX": regex},
|
||||||
|
)
|
||||||
|
await process.wait()
|
||||||
|
if process.returncode != 0:
|
||||||
|
message = (await process.stdout.read()).decode()
|
||||||
|
return False, message
|
||||||
|
return True, "ok"
|
||||||
|
|
||||||
class RegexFilter:
|
class RegexFilter:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, regex,
|
self, regex,
|
||||||
@@ -44,7 +57,6 @@ class RegexFilter:
|
|||||||
self.regex = self.regex.encode()
|
self.regex = self.regex.encode()
|
||||||
if not isinstance(self.regex, bytes):
|
if not isinstance(self.regex, bytes):
|
||||||
raise Exception("Invalid Regex Paramether")
|
raise Exception("Invalid Regex Paramether")
|
||||||
re.compile(self.regex) # raise re.error if it's invalid!
|
|
||||||
case_sensitive = "1" if self.is_case_sensitive else "0"
|
case_sensitive = "1" if self.is_case_sensitive else "0"
|
||||||
if self.input_mode:
|
if self.input_mode:
|
||||||
yield case_sensitive + "C" + self.regex.hex()
|
yield case_sensitive + "C" + self.regex.hex()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
import re
|
|
||||||
import secrets
|
import secrets
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from fastapi import APIRouter, Response, HTTPException
|
from fastapi import APIRouter, Response, HTTPException
|
||||||
@@ -9,6 +8,7 @@ from modules.nfregex.firewall import STATUS, FirewallManager
|
|||||||
from utils.sqlite import SQLite
|
from utils.sqlite import SQLite
|
||||||
from utils import ip_parse, refactor_name, socketio_emit, PortType
|
from utils import ip_parse, refactor_name, socketio_emit, PortType
|
||||||
from utils.models import ResetRequest, StatusMessageModel
|
from utils.models import ResetRequest, StatusMessageModel
|
||||||
|
from modules.nfregex.firegex import test_regex_validity
|
||||||
|
|
||||||
class ServiceModel(BaseModel):
|
class ServiceModel(BaseModel):
|
||||||
status: str
|
status: str
|
||||||
@@ -299,10 +299,9 @@ async def regex_disable(regex_id: int):
|
|||||||
@app.post('/regexes', response_model=StatusMessageModel)
|
@app.post('/regexes', response_model=StatusMessageModel)
|
||||||
async def add_new_regex(form: RegexAddForm):
|
async def add_new_regex(form: RegexAddForm):
|
||||||
"""Add a new regex"""
|
"""Add a new regex"""
|
||||||
try:
|
regex_correct, message = await test_regex_validity(b64decode(form.regex))
|
||||||
re.compile(b64decode(form.regex))
|
if not regex_correct:
|
||||||
except Exception:
|
raise HTTPException(status_code=400, detail=f"Invalid regex: {message}")
|
||||||
raise HTTPException(status_code=400, detail="Invalid regex")
|
|
||||||
try:
|
try:
|
||||||
db.query("INSERT INTO regexes (service_id, regex, mode, is_case_sensitive, active ) VALUES (?, ?, ?, ?, ?);",
|
db.query("INSERT INTO regexes (service_id, regex, mode, is_case_sensitive, active ) VALUES (?, ?, ?, ?, ?);",
|
||||||
form.service_id, form.regex, form.mode, form.is_case_sensitive, True if form.active is None else form.active )
|
form.service_id, form.regex, form.mode, form.is_case_sensitive, True if form.active is None else form.active )
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ def exit_test(code):
|
|||||||
|
|
||||||
#Create new Service
|
#Create new Service
|
||||||
|
|
||||||
|
srvs = firegex.nf_get_services()
|
||||||
|
for ele in srvs:
|
||||||
|
if ele['name'] == args.service_name:
|
||||||
|
firegex.nf_delete_service(ele['service_id'])
|
||||||
|
|
||||||
service_id = firegex.nf_add_service(args.service_name, args.port, "tcp", "127.0.0.1/24")
|
service_id = firegex.nf_add_service(args.service_name, args.port, "tcp", "127.0.0.1/24")
|
||||||
if service_id:
|
if service_id:
|
||||||
puts(f"Sucessfully created service {service_id} ✔", color=colors.green)
|
puts(f"Sucessfully created service {service_id} ✔", color=colors.green)
|
||||||
@@ -52,6 +57,10 @@ else:
|
|||||||
puts("Test Failed: Failed to create service ✗", color=colors.red)
|
puts("Test Failed: Failed to create service ✗", color=colors.red)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
args.port = int(args.port)
|
||||||
|
args.duration = int(args.duration)
|
||||||
|
args.num_of_streams = int(args.num_of_streams)
|
||||||
|
|
||||||
#Start iperf3
|
#Start iperf3
|
||||||
def startServer():
|
def startServer():
|
||||||
server = iperf3.Server()
|
server = iperf3.Server()
|
||||||
@@ -66,6 +75,8 @@ def getReading(port):
|
|||||||
client.duration = args.duration
|
client.duration = args.duration
|
||||||
client.server_hostname = '127.0.0.1'
|
client.server_hostname = '127.0.0.1'
|
||||||
client.port = port
|
client.port = port
|
||||||
|
client.zerocopy = True
|
||||||
|
client.verbose = False
|
||||||
client.protocol = 'tcp'
|
client.protocol = 'tcp'
|
||||||
client.num_streams = args.num_of_streams
|
client.num_streams = args.num_of_streams
|
||||||
return round(client.run().json['end']['sum_received']['bits_per_second']/8e+6 , 3)
|
return round(client.run().json['end']['sum_received']['bits_per_second']/8e+6 , 3)
|
||||||
@@ -74,6 +85,9 @@ server = Process(target=startServer)
|
|||||||
server.start()
|
server.start()
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
|
def gen_regex():
|
||||||
|
regex = secrets.token_hex(8)
|
||||||
|
return base64.b64encode(bytes(regex.encode())).decode()
|
||||||
|
|
||||||
#Get baseline reading
|
#Get baseline reading
|
||||||
puts("Baseline without proxy: ", color=colors.blue, end='')
|
puts("Baseline without proxy: ", color=colors.blue, end='')
|
||||||
@@ -95,7 +109,7 @@ print(f"{results[0]} MB/s")
|
|||||||
|
|
||||||
#Add all the regexs
|
#Add all the regexs
|
||||||
for i in range(1,args.num_of_regexes+1):
|
for i in range(1,args.num_of_regexes+1):
|
||||||
regex = base64.b64encode(bytes(secrets.token_hex(16).encode())).decode()
|
regex = gen_regex()
|
||||||
if not firegex.nf_add_regex(service_id,regex,"B",active=True,is_case_sensitive=False):
|
if not firegex.nf_add_regex(service_id,regex,"B",active=True,is_case_sensitive=False):
|
||||||
puts("Benchmark Failed: Couldn't add the regex ✗", color=colors.red)
|
puts("Benchmark Failed: Couldn't add the regex ✗", color=colors.red)
|
||||||
exit_test(1)
|
exit_test(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user