Starting inserting protocol and ip interface in services

This commit is contained in:
DomySh
2022-07-11 19:56:05 +02:00
parent 61af0fa982
commit c4ab4c628e
11 changed files with 74 additions and 103 deletions

View File

@@ -1,6 +1,6 @@
from base64 import b64decode from base64 import b64decode
import sqlite3, uvicorn, sys, secrets, re, os, asyncio import sqlite3, uvicorn, sys, secrets, re
import httpx, urllib, websockets import httpx, websockets, os, asyncio
from typing import List, Union from typing import List, Union
from fastapi import FastAPI, HTTPException, WebSocket, Depends from fastapi import FastAPI, HTTPException, WebSocket, Depends
from pydantic import BaseModel, BaseSettings from pydantic import BaseModel, BaseSettings
@@ -333,6 +333,8 @@ class ServiceAddForm(BaseModel):
name: str name: str
port: int port: int
ipv6: bool ipv6: bool
proto: str
ip_int: str
class ServiceAddResponse(BaseModel): class ServiceAddResponse(BaseModel):
status:str status:str
@@ -341,22 +343,28 @@ class ServiceAddResponse(BaseModel):
@app.post('/api/services/add', response_model=ServiceAddResponse) @app.post('/api/services/add', response_model=ServiceAddResponse)
async def add_new_service(form: ServiceAddForm, auth: bool = Depends(is_loggined)): async def add_new_service(form: ServiceAddForm, auth: bool = Depends(is_loggined)):
"""Add a new service""" """Add a new service"""
import time if form.ipv6:
if not checkIpv6(form.ip_int):
return {"status":"Invalid IPv6 address"}
else:
if not checkIpv4(form.ip_int):
return {"status":"Invalid IPv4 address"}
if form.proto not in ["tcp", "udp"]:
return {"status":"Invalid protocol"}
srv_id = None srv_id = None
try: try:
srv_id = str(form.port)+"::"+("ipv6" if form.ipv6 else "ipv4") srv_id = gen_service_id(db)
db.query("INSERT INTO services (service_id ,name, port, ipv6, status) VALUES (?, ?, ?, ?, ?)", db.query("INSERT INTO services (service_id ,name, port, ipv6, status) VALUES (?, ?, ?, ?, ?)",
srv_id, refactor_name(form.name), form.port, form.ipv6, STATUS.STOP) srv_id, refactor_name(form.name), form.port, form.ipv6, STATUS.STOP)
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
return {'status': 'Name or/and ports of the service has been already assigned'} return {'status': 'Name or/and ports of the service has been already assigned'}
await firewall.reload() await firewall.reload()
init_t = time.time()
await refresh_frontend() await refresh_frontend()
return {'status': 'ok', 'service_id': srv_id} return {'status': 'ok', 'service_id': srv_id}
async def frontend_debug_proxy(path): async def frontend_debug_proxy(path):
httpc = httpx.AsyncClient() httpc = httpx.AsyncClient()
req = httpc.build_request("GET",urllib.parse.urljoin(f"http://127.0.0.1:{os.getenv('F_PORT','3000')}", path)) req = httpc.build_request("GET",f"http://127.0.0.1:{os.getenv('F_PORT','3000')}/"+path)
resp = await httpc.send(req, stream=True) resp = await httpc.send(req, stream=True)
return StreamingResponse(resp.aiter_bytes(),status_code=resp.status_code) return StreamingResponse(resp.aiter_bytes(),status_code=resp.status_code)

View File

@@ -114,13 +114,14 @@ class Interceptor:
def _start_queue(self,func,n_threads): def _start_queue(self,func,n_threads):
def func_wrap(ll_data, ll_proto_id, data, ctx, *args): def func_wrap(ll_data, ll_proto_id, data, ctx, *args):
pkt_parsed = ip6.IP6(data) if self.ipv6 else ip.IP(data) pkt_parsed = ip6.IP6(data) if self.ipv6 else ip.IP(data)
try: try:
level4 = None level4 = None
if self.proto == ProtoTypes.TCP: level4 = pkt_parsed[tcp.TCP].body_bytes if self.proto == ProtoTypes.TCP: level4 = pkt_parsed[tcp.TCP].body_bytes
elif self.proto == ProtoTypes.UDP: level4 = pkt_parsed[udp.UDP].body_bytes elif self.proto == ProtoTypes.UDP: level4 = pkt_parsed[udp.UDP].body_bytes
if level4: if level4:
if func(level4): if func(level4):
return pkt_parsed.bin(), interceptor.NF_ACCEPT return data, interceptor.NF_ACCEPT
elif self.proto == ProtoTypes.TCP: elif self.proto == ProtoTypes.TCP:
pkt_parsed[tcp.TCP].flags &= 0x00 pkt_parsed[tcp.TCP].flags &= 0x00
pkt_parsed[tcp.TCP].flags |= tcp.TH_FIN | tcp.TH_ACK pkt_parsed[tcp.TCP].flags |= tcp.TH_FIN | tcp.TH_ACK

View File

@@ -1,11 +1,21 @@
import traceback import traceback
from typing import Dict from typing import Dict
from proxy import Filter, Proxy from proxy import Filter, Proxy
import os, sqlite3, socket, asyncio import os, sqlite3, socket, asyncio, re
import secrets
from base64 import b64decode from base64 import b64decode
LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1")) LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1"))
regex_ipv6 = r"^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$";
regex_ipv4 = r"^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))?$"
def checkIpv6(ip:str):
return bool(re.match(regex_ipv6, ip))
def checkIpv4(ip:str):
return bool(re.match(regex_ipv4, ip))
class SQLite(): class SQLite():
def __init__(self, db_name) -> None: def __init__(self, db_name) -> None:
self.conn = None self.conn = None
@@ -239,4 +249,11 @@ class ProxyManager:
def refactor_name(name:str): def refactor_name(name:str):
name = name.strip() name = name.strip()
while " " in name: name = name.replace(" "," ") while " " in name: name = name.replace(" "," ")
return name return name
def gen_service_id(db):
while True:
res = secrets.token_hex(8)
if len(db.query('SELECT 1 FROM services WHERE service_id = ?;', res)) == 0:
break
return res

View File

@@ -1,13 +1,13 @@
{ {
"files": { "files": {
"main.css": "/static/css/main.08225a85.css", "main.css": "/static/css/main.08225a85.css",
"main.js": "/static/js/main.852729b0.js", "main.js": "/static/js/main.3f472f16.js",
"index.html": "/index.html", "index.html": "/index.html",
"main.08225a85.css.map": "/static/css/main.08225a85.css.map", "main.08225a85.css.map": "/static/css/main.08225a85.css.map",
"main.852729b0.js.map": "/static/js/main.852729b0.js.map" "main.3f472f16.js.map": "/static/js/main.3f472f16.js.map"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.08225a85.css", "static/css/main.08225a85.css",
"static/js/main.852729b0.js" "static/js/main.3f472f16.js"
] ]
} }

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="manifest" href="/site.webmanifest"><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#FFFFFFFF"/><meta name="description" content="Firegex by Pwnzer0tt1"/><title>Firegex</title><script defer="defer" src="/static/js/main.852729b0.js"></script><link href="/static/css/main.08225a85.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html> <!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"><link rel="manifest" href="/site.webmanifest"><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#FFFFFFFF"/><meta name="description" content="Firegex by Pwnzer0tt1"/><title>Firegex</title><script defer="defer" src="/static/js/main.3f472f16.js"></script><link href="/static/css/main.08225a85.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

View File

@@ -1,70 +0,0 @@
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* React Router v6.3.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +1,14 @@
import { Button, Group, NumberInput, Space, TextInput, Notification, Modal, Switch } from '@mantine/core'; import { Button, Group, NumberInput, Space, TextInput, Notification, Modal, Switch, SegmentedControl } from '@mantine/core';
import { useForm } from '@mantine/hooks'; import { useForm } from '@mantine/hooks';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { addservice, okNotify, startservice } from '../js/utils'; import { addservice, okNotify, startservice, regex_ipv4, regex_ipv6 } from '../js/utils';
import { ImCross } from "react-icons/im" import { ImCross } from "react-icons/im"
type ServiceAddForm = { type ServiceAddForm = {
name:string, name:string,
port:number, port:number,
ipv6:boolean, proto:string,
ip_int:string,
autostart: boolean, autostart: boolean,
} }
@@ -17,12 +18,15 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
initialValues: { initialValues: {
name:"", name:"",
port:8080, port:8080,
ipv6:false, ip_int:"127.0.0.1",
proto:"tcp",
autostart: true autostart: true
}, },
validationRules:{ validationRules:{
name: (value) => value !== ""?true:false, name: (value) => value !== ""?true:false,
port: (value) => value>0 && value<65536, port: (value) => value>0 && value<65536,
proto: (value) => ["tcp","udp"].includes(value),
ip_int: (value) => value.match(regex_ipv6)?true:false || value.match(regex_ipv4)?true:false
} }
}) })
@@ -35,9 +39,10 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
const [submitLoading, setSubmitLoading] = useState(false) const [submitLoading, setSubmitLoading] = useState(false)
const [error, setError] = useState<string|null>(null) const [error, setError] = useState<string|null>(null)
const submitRequest = ({ name, port, autostart, ipv6 }:ServiceAddForm) =>{ const submitRequest = ({ name, port, autostart, proto, ip_int }:ServiceAddForm) =>{
setSubmitLoading(true) setSubmitLoading(true)
addservice({name, port, ipv6}).then( res => { const ipv6 = ip_int.match(regex_ipv4)?false:true
addservice({name, port, ipv6, proto, ip_int }).then( res => {
if (res.status === "ok" && res.service_id){ if (res.status === "ok" && res.service_id){
setSubmitLoading(false) setSubmitLoading(false)
close(); close();
@@ -53,7 +58,6 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
}) })
} }
return <Modal size="xl" title="Add a new service" opened={opened} onClose={close} closeOnClickOutside={false} centered> return <Modal size="xl" title="Add a new service" opened={opened} onClose={close} closeOnClickOutside={false} centered>
<form onSubmit={form.onSubmit(submitRequest)}> <form onSubmit={form.onSubmit(submitRequest)}>
<TextInput <TextInput
@@ -63,6 +67,14 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
/> />
<Space h="md" /> <Space h="md" />
<TextInput
label="Public IP Interface (ipv4/ipv6 + CIDR allowed)"
placeholder="10.40.20.0/8"
{...form.getInputProps('ip_int')}
/>
<Space h="md" />
<NumberInput <NumberInput
placeholder="8080" placeholder="8080"
min={1} min={1}
@@ -70,20 +82,23 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
label="Public Service port" label="Public Service port"
{...form.getInputProps('port')} {...form.getInputProps('port')}
/> />
<Space h="md" />
<SegmentedControl
data={[
{ label: 'TCP', value: 'tcp' },
{ label: 'UDP', value: 'udp' },
]}
{...form.getInputProps('proto')}
/>
<Space h="xl" /> <Space h="xl" />
<Switch <Switch
label="Auto-Start Service" label="Auto-Start Service"
{...form.getInputProps('autostart', { type: 'checkbox' })} {...form.getInputProps('autostart', { type: 'checkbox' })}
/> />
<Space h="sm" />
<Switch
label="Filter on Ipv6"
{...form.getInputProps('ipv6', { type: 'checkbox' })}
/>
<Group position="right" mt="md"> <Group position="right" mt="md">
<Button loading={submitLoading} type="submit">Add Service</Button> <Button loading={submitLoading} type="submit">Add Service</Button>

View File

@@ -18,7 +18,8 @@ export type ServiceAddForm = {
name:string, name:string,
port:number, port:number,
ipv6:boolean, ipv6:boolean,
internalPort?:number proto:string,
ip_int:string,
} }
export type ServiceAddResponse = { export type ServiceAddResponse = {

View File

@@ -7,6 +7,8 @@ var Buffer = require('buffer').Buffer
export const eventUpdateName = "update-info" export const eventUpdateName = "update-info"
export const regex_ipv6 = "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$";
export const regex_ipv4 = "^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))?$"
export async function getapi(path:string):Promise<any>{ export async function getapi(path:string):Promise<any>{
@@ -145,6 +147,7 @@ export async function serviceregexlist(service_id:string){
export function errorNotify(title:string, description:string ){ export function errorNotify(title:string, description:string ){
showNotification({ showNotification({
autoClose: 2000, autoClose: 2000,