mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
Merge branch '3.1' into image-manager-out-of-sync
This commit is contained in:
129 files changed
+944
-863
No files matched your search
@@ -32,16 +32,14 @@ import asyncio
|
||||
import aiofiles
|
||||
from concurrent import futures
|
||||
|
||||
from zipfile import (
|
||||
structCentralDir,
|
||||
structEndArchive64,
|
||||
structEndArchive,
|
||||
structEndArchive64Locator,
|
||||
stringCentralDir,
|
||||
stringEndArchive64,
|
||||
stringEndArchive,
|
||||
stringEndArchive64Locator,
|
||||
)
|
||||
structCentralDir = "<4s4B4HL2L5H2L"
|
||||
structEndArchive64 = "<4sQ2H2L4Q"
|
||||
structEndArchive = b"<4s4H2LH"
|
||||
structEndArchive64Locator = "<4sLQL"
|
||||
stringCentralDir = b"PK\x01\x02"
|
||||
stringEndArchive64 = b"PK\x06\x06"
|
||||
stringEndArchive = b"PK\x05\x06"
|
||||
stringEndArchive64Locator = b"PK\x06\x07"
|
||||
|
||||
|
||||
ZIP_ZSTANDARD = 93 # zstandard is supported by WinZIP v24 and later, PowerArchiver 2021 and 7-Zip-zstd
|
||||
|
||||
@@ -48,7 +48,7 @@ def init_encryption(secrets_dir: str) -> None:
|
||||
os.makedirs(secrets_dir, exist_ok=True)
|
||||
with open(encryption_key_path, "w", encoding="utf-8") as f:
|
||||
# Use Fernet's base64-encoded key format
|
||||
f.write(key.decode() if isinstance(key, bytes) else key)
|
||||
f.write(key.decode())
|
||||
# Set restrictive permissions (owner read/write only)
|
||||
os.chmod(encryption_key_path, 0o600)
|
||||
except OSError as e:
|
||||
@@ -58,7 +58,7 @@ def init_encryption(secrets_dir: str) -> None:
|
||||
try:
|
||||
with open(encryption_key_path, encoding="utf-8") as f:
|
||||
key_content = f.read().strip()
|
||||
key_bytes = key_content.encode() if isinstance(key_content, str) else key_content
|
||||
key_bytes = key_content.encode()
|
||||
_fernet = Fernet(key_bytes)
|
||||
log.debug("Encryption initialized successfully")
|
||||
except OSError as e:
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import aiohttp
|
||||
from pydantic import SecretStr
|
||||
import socket
|
||||
|
||||
import logging
|
||||
@@ -28,7 +29,7 @@ class HTTPClient:
|
||||
HTTP client for request to computes and external services.
|
||||
"""
|
||||
|
||||
_aiohttp_client: aiohttp.ClientSession = None
|
||||
_aiohttp_client: aiohttp.ClientSession | None = None
|
||||
|
||||
@classmethod
|
||||
def get_client(cls, ssl_context=None) -> aiohttp.ClientSession:
|
||||
@@ -44,15 +45,21 @@ class HTTPClient:
|
||||
cls._aiohttp_client = None
|
||||
|
||||
@classmethod
|
||||
def request(cls, method: str, url: str, user: str = None, password: str = None, ssl_context=None, **kwargs):
|
||||
def request(
|
||||
cls,
|
||||
method: str,
|
||||
url: str,
|
||||
user: str | None = None,
|
||||
password: SecretStr | None = None,
|
||||
ssl_context=None,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
client = cls.get_client(ssl_context=ssl_context)
|
||||
basic_auth = None
|
||||
if user:
|
||||
if not password:
|
||||
password = ""
|
||||
try:
|
||||
basic_auth = aiohttp.BasicAuth(user, password.get_secret_value(), "utf-8")
|
||||
basic_auth = aiohttp.BasicAuth(user, password.get_secret_value() if password else "", "utf-8")
|
||||
except ValueError as e:
|
||||
log.error(f"Basic authentication set-up error: {e}")
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ async def read_image_info(path: str, expected_image_type: str = None, allow_raw_
|
||||
raise InvalidImageError(f"Cannot read image '{path}': {e}") from e
|
||||
|
||||
|
||||
async def discover_images(image_type: str, skip_image_paths: list = None) -> List[dict]:
|
||||
async def discover_images(image_type: str, skip_image_paths: list | None = None) -> List[dict]:
|
||||
"""
|
||||
Scan directories for available images
|
||||
"""
|
||||
|
||||
@@ -15,7 +15,7 @@ class FilterValidationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def validate_bpf_syntax(bpf_expression: str) -> Dict[str, Optional[str]]:
|
||||
def validate_bpf_syntax(bpf_expression: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Validate BPF filter expression syntax using tcpdump.
|
||||
|
||||
@@ -78,7 +78,7 @@ def validate_filter_parameters(filter_type: str, values: List[Any]) -> None:
|
||||
"""
|
||||
|
||||
# Define validation rules based on ubridge implementation
|
||||
VALIDATION_RULES = {
|
||||
VALIDATION_RULES: Dict[str, Dict[str, Any]] = {
|
||||
"frequency_drop": {
|
||||
"params_count": 1,
|
||||
"ranges": [(-1, 32767)], # min, max
|
||||
@@ -173,11 +173,11 @@ def filter_inactive_filters(filters: Dict[str, List[Any]]) -> Dict[str, List[Any
|
||||
|
||||
active_filters = {}
|
||||
for filter_type, values in filters.items():
|
||||
if not values or (isinstance(values, list) and len(values) == 0):
|
||||
if not values:
|
||||
continue
|
||||
|
||||
# Normalize values (strip strings, convert to int)
|
||||
normalized_values = []
|
||||
normalized_values: List[Any] = []
|
||||
for value in values:
|
||||
if isinstance(value, str):
|
||||
normalized_values.append(value.strip("\n "))
|
||||
@@ -228,7 +228,7 @@ def validate_all_filters(filters: Dict[str, List[Any]]) -> None:
|
||||
return
|
||||
|
||||
for filter_type, values in filters.items():
|
||||
if not values or (isinstance(values, list) and len(values) == 0):
|
||||
if not values:
|
||||
continue
|
||||
|
||||
validate_filter_parameters(filter_type, values)
|
||||
@@ -25,7 +25,6 @@ Similar pattern to VNC console implementation in base_node.py:
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import aiohttp
|
||||
@@ -39,7 +38,7 @@ log = logging.getLogger(__name__)
|
||||
async def websocket_proxy(
|
||||
client_ws: WebSocket,
|
||||
target_url: str,
|
||||
requested_protocols: list = None,
|
||||
requested_protocols: Optional[list] = None,
|
||||
buffer_size: int = 65536,
|
||||
timeout: Optional[float] = None,
|
||||
) -> None:
|
||||
@@ -59,7 +58,7 @@ async def websocket_proxy(
|
||||
Raises:
|
||||
aiohttp.ClientError: If connection to target fails
|
||||
"""
|
||||
client_info = f"{client_ws.client.host}:{client_ws.client.port}"
|
||||
client_info = f"{client_ws.client.host}:{client_ws.client.port}" if client_ws.client else "unknown"
|
||||
|
||||
async def forward_client_to_target(target_ws):
|
||||
"""Client → Target: Forward binary WebSocket data."""
|
||||
@@ -101,25 +100,20 @@ async def websocket_proxy(
|
||||
subprotocols = requested_protocols or ["binary"]
|
||||
log.info(f"Client requested subprotocols: {subprotocols}")
|
||||
|
||||
timeout_config = {}
|
||||
if timeout:
|
||||
timeout_config = {"timeout": aiohttp.ClientTimeout(total=timeout)}
|
||||
session_timeout = aiohttp.ClientTimeout(total=timeout) if timeout else None
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.ws_connect(target_url, protocols=subprotocols, **timeout_config) as target_ws:
|
||||
async with aiohttp.ClientSession(timeout=session_timeout) as session:
|
||||
async with session.ws_connect(target_url, protocols=subprotocols) as target_ws:
|
||||
negotiated_protocol = target_ws.protocol
|
||||
log.info(f"Target WebSocket negotiated protocol: {negotiated_protocol}")
|
||||
log.info(f"WebSocket proxy established: {client_info} → {target_url}")
|
||||
|
||||
# Run both forwarding tasks in parallel
|
||||
# Similar pattern to base_node.py VNC implementation
|
||||
if sys.version_info >= (3, 11, 0):
|
||||
aws = [
|
||||
asyncio.create_task(forward_client_to_target(target_ws)),
|
||||
asyncio.create_task(forward_target_to_client(target_ws)),
|
||||
]
|
||||
else:
|
||||
aws = [forward_client_to_target(target_ws), forward_target_to_client(target_ws)]
|
||||
aws = [
|
||||
asyncio.create_task(forward_client_to_target(target_ws)),
|
||||
asyncio.create_task(forward_target_to_client(target_ws)),
|
||||
]
|
||||
|
||||
try:
|
||||
done, pending = await asyncio.wait(aws, return_when=asyncio.FIRST_COMPLETED)
|
||||
@@ -146,7 +140,7 @@ async def websocket_proxy(
|
||||
async def websocket_proxy_with_manual_accept(
|
||||
client_ws: StarletteWebSocket,
|
||||
target_url: str,
|
||||
requested_protocols: list = None,
|
||||
requested_protocols: Optional[list] = None,
|
||||
buffer_size: int = 65536,
|
||||
timeout: Optional[float] = None,
|
||||
) -> None:
|
||||
@@ -167,7 +161,7 @@ async def websocket_proxy_with_manual_accept(
|
||||
Raises:
|
||||
aiohttp.ClientError: If connection to target fails
|
||||
"""
|
||||
client_info = f"{client_ws.client.host}:{client_ws.client.port}" if hasattr(client_ws, "client") else "unknown"
|
||||
client_info = f"{client_ws.client.host}:{client_ws.client.port}" if client_ws.client else "unknown"
|
||||
|
||||
async def forward_client_to_target(target_ws):
|
||||
"""Client → Target: Forward binary WebSocket data."""
|
||||
@@ -213,13 +207,11 @@ async def websocket_proxy_with_manual_accept(
|
||||
subprotocols = requested_protocols or ["binary"]
|
||||
log.info(f"Client requested subprotocols: {subprotocols}")
|
||||
|
||||
timeout_config = {}
|
||||
if timeout:
|
||||
timeout_config = {"timeout": aiohttp.ClientTimeout(total=timeout)}
|
||||
session_timeout = aiohttp.ClientTimeout(total=timeout) if timeout else None
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with aiohttp.ClientSession(timeout=session_timeout) as session:
|
||||
log.info(f"About to ws_connect to {target_url}")
|
||||
ws_conn = session.ws_connect(target_url, protocols=subprotocols, **timeout_config)
|
||||
ws_conn = session.ws_connect(target_url, protocols=subprotocols)
|
||||
log.info("ws_connect coroutine created, about to enter")
|
||||
async with ws_conn as target_ws:
|
||||
negotiated_protocol = target_ws.protocol
|
||||
@@ -235,13 +227,10 @@ async def websocket_proxy_with_manual_accept(
|
||||
log.info(f"WebSocket proxy established: {client_info} → {target_url}")
|
||||
|
||||
# Run both forwarding tasks in parallel
|
||||
if sys.version_info >= (3, 11, 0):
|
||||
aws = [
|
||||
asyncio.create_task(forward_client_to_target(target_ws)),
|
||||
asyncio.create_task(forward_target_to_client(target_ws)),
|
||||
]
|
||||
else:
|
||||
aws = [forward_client_to_target(target_ws), forward_target_to_client(target_ws)]
|
||||
aws = [
|
||||
asyncio.create_task(forward_client_to_target(target_ws)),
|
||||
asyncio.create_task(forward_target_to_client(target_ws)),
|
||||
]
|
||||
|
||||
log.info(f"About to call asyncio.wait with {len(aws)} tasks")
|
||||
try:
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import functools
|
||||
from typing import Any
|
||||
|
||||
|
||||
class patch:
|
||||
originals = {}
|
||||
originals: dict[str, Any] = {}
|
||||
|
||||
def __init__(self, host, name):
|
||||
self.host = host
|
||||
|
||||
Reference in new issue
Block a user