diff --git a/backend/app.py b/backend/app.py index 242251c..58ec112 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,6 +1,6 @@ from base64 import b64decode -import sqlite3, uvicorn, sys, secrets, re, os, asyncio -import httpx, urllib, websockets +import sqlite3, uvicorn, sys, secrets, re +import httpx, websockets, os, asyncio from typing import List, Union from fastapi import FastAPI, HTTPException, WebSocket, Depends from pydantic import BaseModel, BaseSettings @@ -333,6 +333,8 @@ class ServiceAddForm(BaseModel): name: str port: int ipv6: bool + proto: str + ip_int: str class ServiceAddResponse(BaseModel): status:str @@ -341,22 +343,28 @@ class ServiceAddResponse(BaseModel): @app.post('/api/services/add', response_model=ServiceAddResponse) async def add_new_service(form: ServiceAddForm, auth: bool = Depends(is_loggined)): """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 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 (?, ?, ?, ?, ?)", srv_id, refactor_name(form.name), form.port, form.ipv6, STATUS.STOP) except sqlite3.IntegrityError: return {'status': 'Name or/and ports of the service has been already assigned'} await firewall.reload() - init_t = time.time() await refresh_frontend() return {'status': 'ok', 'service_id': srv_id} async def frontend_debug_proxy(path): 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) return StreamingResponse(resp.aiter_bytes(),status_code=resp.status_code) diff --git a/backend/proxy.py b/backend/proxy.py index 13ede59..a6d1484 100755 --- a/backend/proxy.py +++ b/backend/proxy.py @@ -114,13 +114,14 @@ class Interceptor: def _start_queue(self,func,n_threads): def func_wrap(ll_data, ll_proto_id, data, ctx, *args): pkt_parsed = ip6.IP6(data) if self.ipv6 else ip.IP(data) + try: level4 = None if self.proto == ProtoTypes.TCP: level4 = pkt_parsed[tcp.TCP].body_bytes elif self.proto == ProtoTypes.UDP: level4 = pkt_parsed[udp.UDP].body_bytes if level4: if func(level4): - return pkt_parsed.bin(), interceptor.NF_ACCEPT + return data, interceptor.NF_ACCEPT elif self.proto == ProtoTypes.TCP: pkt_parsed[tcp.TCP].flags &= 0x00 pkt_parsed[tcp.TCP].flags |= tcp.TH_FIN | tcp.TH_ACK diff --git a/backend/utils.py b/backend/utils.py index 9ad2075..acc52e3 100755 --- a/backend/utils.py +++ b/backend/utils.py @@ -1,11 +1,21 @@ import traceback from typing import Dict from proxy import Filter, Proxy -import os, sqlite3, socket, asyncio +import os, sqlite3, socket, asyncio, re +import secrets from base64 import b64decode 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(): def __init__(self, db_name) -> None: self.conn = None @@ -239,4 +249,11 @@ class ProxyManager: def refactor_name(name:str): name = name.strip() while " " in name: name = name.replace(" "," ") - return name \ No newline at end of file + 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 \ No newline at end of file diff --git a/frontend/build/asset-manifest.json b/frontend/build/asset-manifest.json index 98e2fc2..f98b7b4 100644 --- a/frontend/build/asset-manifest.json +++ b/frontend/build/asset-manifest.json @@ -1,13 +1,13 @@ { "files": { "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", "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": [ "static/css/main.08225a85.css", - "static/js/main.852729b0.js" + "static/js/main.3f472f16.js" ] } \ No newline at end of file diff --git a/frontend/build/index.html b/frontend/build/index.html index 7db1da2..5d679c6 100644 --- a/frontend/build/index.html +++ b/frontend/build/index.html @@ -1 +1 @@ -
a||125d?(a.sortIndex=c,f(t,a),null===h(r)&&a===h(t)&&(B?(E(L),L=-1):B=!0,K(H,c-d))):(a.sortIndex=e,f(r,a),A||z||(A=!0,I(J)));return a};\nexports.unstable_shouldYield=M;exports.unstable_wrapCallback=function(a){var b=y;return function(){var c=y;y=b;try{return a.apply(this,arguments)}finally{y=c}}};\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/scheduler.production.min.js');\n} else {\n module.exports = require('./cjs/scheduler.development.js');\n}\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = function(module) {\n\tvar getter = module && module.__esModule ?\n\t\tfunction() { return module['default']; } :\n\t\tfunction() { return module; };\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = function(exports, definition) {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }","// define __esModule on exports\n__webpack_require__.r = function(exports) {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","export default function _arrayLikeToArray(arr, len) {\n if (len == null || len > arr.length) len = arr.length;\n\n for (var i = 0, arr2 = new Array(len); i < len; i++) {\n arr2[i] = arr[i];\n }\n\n return arr2;\n}","import arrayLikeToArray from \"./arrayLikeToArray.js\";\nexport default function _unsupportedIterableToArray(o, minLen) {\n if (!o) return;\n if (typeof o === \"string\") return arrayLikeToArray(o, minLen);\n var n = Object.prototype.toString.call(o).slice(8, -1);\n if (n === \"Object\" && o.constructor) n = o.constructor.name;\n if (n === \"Map\" || n === \"Set\") return Array.from(o);\n if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return arrayLikeToArray(o, minLen);\n}","import arrayWithHoles from \"./arrayWithHoles.js\";\nimport iterableToArrayLimit from \"./iterableToArrayLimit.js\";\nimport unsupportedIterableToArray from \"./unsupportedIterableToArray.js\";\nimport nonIterableRest from \"./nonIterableRest.js\";\nexport default function _slicedToArray(arr, i) {\n return arrayWithHoles(arr) || iterableToArrayLimit(arr, i) || unsupportedIterableToArray(arr, i) || nonIterableRest();\n}","export default function _arrayWithHoles(arr) {\n if (Array.isArray(arr)) return arr;\n}","export default function _iterableToArrayLimit(arr, i) {\n var _i = arr == null ? null : typeof Symbol !== \"undefined\" && arr[Symbol.iterator] || arr[\"@@iterator\"];\n\n if (_i == null) return;\n var _arr = [];\n var _n = true;\n var _d = false;\n\n var _s, _e;\n\n try {\n for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {\n _arr.push(_s.value);\n\n if (i && _arr.length === i) break;\n }\n } catch (err) {\n _d = true;\n _e = err;\n } finally {\n try {\n if (!_n && _i[\"return\"] != null) _i[\"return\"]();\n } finally {\n if (_d) throw _e;\n }\n }\n\n return _arr;\n}","export default function _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}","export default function _extends() {\n _extends = Object.assign ? Object.assign.bind() : function (target) {\n for (var i = 1; i < arguments.length; i++) {\n var source = arguments[i];\n\n for (var key in source) {\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n target[key] = source[key];\n }\n }\n }\n\n return target;\n };\n return _extends.apply(this, arguments);\n}","import * as React from \"react\";\nimport type { History, Location } from \"history\";\nimport { Action as NavigationType } from \"history\";\n\nimport type { RouteMatch } from \"./router\";\n\n/**\n * A Navigator is a \"location changer\"; it's how you get to different locations.\n *\n * Every history instance conforms to the Navigator interface, but the\n * distinction is useful primarily when it comes to the low-level