Introduced MTU tools. MTU set for session configs automatically, for systemwide the testing is setup but not yet implemented. Also introduced server reply objects and a new v2 wg endpoint that doesn't require location codes.
This commit is contained in:
parent
10e46cd8ea
commit
9e4fd54eb6
6 changed files with 281 additions and 49 deletions
|
|
@ -40,6 +40,7 @@ class ResultError(Enum):
|
||||||
WRONG_LOCATION = "wrong_location"
|
WRONG_LOCATION = "wrong_location"
|
||||||
SERVER_SIDE = "server_side"
|
SERVER_SIDE = "server_side"
|
||||||
UNPAID = "unpaid"
|
UNPAID = "unpaid"
|
||||||
|
MTU = "mtu"
|
||||||
NAVIGATE_TO = "navigate_to"
|
NAVIGATE_TO = "navigate_to"
|
||||||
UNKNOWN = "unknown"
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
|
from core.Errors import InvalidSubscriptionError
|
||||||
from core.models.manage.validate_dataclass import validate_model
|
from core.models.manage.validate_dataclass import validate_model
|
||||||
from core.models.pydantic_models.ServerReply import ServerReply, ServerError
|
from core.models.pydantic_models.ServerReply import ServerReply, ServerError
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
from core.services.networking.api_requests.ApiResponseModel import ApiResponse
|
from core.services.networking.api_requests.ApiResponseModel import ApiResponse
|
||||||
|
from core.observers.ConnectionObserver import ConnectionObserver
|
||||||
|
from core.services.networking.httpx import connect
|
||||||
from core.errors.logger import logger
|
from core.errors.logger import logger
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
class ApiCall(Enum):
|
||||||
|
GET = "get"
|
||||||
|
POST = "post"
|
||||||
|
|
||||||
# Translate Server-side to client-side errors in THIS CONTEXT,
|
# Translate Server-side to client-side errors in THIS CONTEXT,
|
||||||
TRANSLATION_DICT = {
|
TRANSLATION_DICT = {
|
||||||
ServerError.WRONG_LOCATION: ResultError.WRONG_LOCATION,
|
ServerError.WRONG_LOCATION: ResultError.WRONG_LOCATION,
|
||||||
|
|
@ -79,3 +87,81 @@ def invalid_server_format(error_msg: Optional[str] = None) -> Result:
|
||||||
error_msg = "The server gave an invalid response format."
|
error_msg = "The server gave an invalid response format."
|
||||||
return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message=error_msg)
|
return Result(valid=False, error_type=ResultError.INVALID_API_REPLY, message=error_msg)
|
||||||
|
|
||||||
|
|
||||||
|
def api_call_and_extraction(
|
||||||
|
desired_type: str,
|
||||||
|
method: ApiCall,
|
||||||
|
url: str,
|
||||||
|
connection_observer: ConnectionObserver,
|
||||||
|
payload: dict,
|
||||||
|
billing_code: Optional[str] = None
|
||||||
|
) -> Result:
|
||||||
|
"""Do an API call, extract the data, and if there's an error, then translate the server's reply to a Result Object"""
|
||||||
|
|
||||||
|
#-----------------------------
|
||||||
|
# SEND TO API
|
||||||
|
#-----------------------------
|
||||||
|
api_response = connect.single_endpoint(
|
||||||
|
method=method.value,
|
||||||
|
url=url,
|
||||||
|
observer=connection_observer,
|
||||||
|
payload=payload,
|
||||||
|
billing_code=billing_code
|
||||||
|
)
|
||||||
|
|
||||||
|
if not api_response:
|
||||||
|
error_msg = "Complete failure of the function to produce a valid api response"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
|
||||||
|
|
||||||
|
#-----------------------------
|
||||||
|
# EVALUATE CONNECTION FAILURE
|
||||||
|
#-----------------------------
|
||||||
|
# Layer 1: API failure:
|
||||||
|
if not api_response.valid:
|
||||||
|
|
||||||
|
# legacy system
|
||||||
|
if api_result.error_type == ErrorType.AUTHENTICATION_ERROR:
|
||||||
|
logger.error(f"Server is giving an Authentication error for billing ID: {billing_code}")
|
||||||
|
raise InvalidSubscriptionError(f"Invalid Subscription for {billing_code}")
|
||||||
|
|
||||||
|
error_msg = f"Connection/500s Error prevented the server from replying: {api_response.error_type} {api_response.message}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return Result(valid=False, error_type=ResultError.CONNECTION, message=error_msg)
|
||||||
|
|
||||||
|
#-----------------------------
|
||||||
|
# EVALUATE SERVER-SIDE ISSUES (assuming it replied)
|
||||||
|
#-----------------------------
|
||||||
|
# Layer 2: Validity of Server's Reply:
|
||||||
|
raw_reply = api_response.data
|
||||||
|
logger.info("Validating the Validity of the Server's Reply..")
|
||||||
|
worked, server_reply = validate_model(ServerReply, raw_reply)
|
||||||
|
|
||||||
|
error_msg = f"Invalid Structure for ServerReply! The raw data is {raw_reply}"
|
||||||
|
|
||||||
|
if not worked:
|
||||||
|
logger.error(error_msg)
|
||||||
|
return invalid_server_format()
|
||||||
|
|
||||||
|
if not server_reply:
|
||||||
|
logger.error(error_msg)
|
||||||
|
return invalid_server_format()
|
||||||
|
|
||||||
|
if not server_reply.valid:
|
||||||
|
error_message = f"Server reply was correctly structured, but the actual invoice failed to be created. The raw data is {raw_reply}"
|
||||||
|
logger.error(error_message)
|
||||||
|
# translate by enum error code:
|
||||||
|
return translate_server_failure(server_reply)
|
||||||
|
|
||||||
|
if not hasattr(server_reply, desired_type):
|
||||||
|
error_msg = "Developer Error: You're asking for a data type that isn't in the object!"
|
||||||
|
logger.error(error_msg)
|
||||||
|
return Result(valid=False, error_type=ResultError.INVALID_INPUT, message=error_msg)
|
||||||
|
|
||||||
|
data = getattr(server_reply, desired_type)
|
||||||
|
if data:
|
||||||
|
return Result(valid=True, data=data)
|
||||||
|
else:
|
||||||
|
return Result(valid=False, error_type=ResultError.MISSING_DATA, message="Server Replied it was valid, but failed to provide the data.")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ class ServerReply():
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
model_version: int = 1
|
model_version: int = 1
|
||||||
data: Optional[dict] = None
|
data: Optional[dict] = None
|
||||||
|
conf: str = None
|
||||||
payment_status: Optional[bool] = None
|
payment_status: Optional[bool] = None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,31 @@
|
||||||
from core.services.networking.httpx import connect
|
|
||||||
from core.services.networking.api_requests.ApiResponseModel import ErrorType
|
from core.models.manage.server_reply_tools import api_call_and_extraction, ApiCall
|
||||||
from core.models.session.SessionProfile import SessionProfile
|
# from core.services.networking.httpx import connect
|
||||||
from core.models.system.SystemProfile import SystemProfile
|
#from core.services.networking.api_requests.ApiResponseModel import ErrorType
|
||||||
from core.Errors import MissingSubscriptionError, MissingLocationError, InvalidSubscriptionError
|
from core.models.BaseProfile import BaseProfile as Profile
|
||||||
|
from core.models.BaseProfile import ProfileType
|
||||||
|
|
||||||
|
# from core.models.session.SessionProfile import SessionProfile
|
||||||
|
# from core.models.system.SystemProfile import SystemProfile
|
||||||
|
from core.Errors import MissingSubscriptionError, MissingLocationError
|
||||||
|
#, InvalidSubscriptionError
|
||||||
# from core.services.WebServiceApiService import WebServiceApiService
|
# from core.services.WebServiceApiService import WebServiceApiService
|
||||||
# from core.controllers.ConnectionController import ConnectionController
|
# from core.controllers.ConnectionController import ConnectionController
|
||||||
from core.observers.ConnectionObserver import ConnectionObserver
|
from core.observers.ConnectionObserver import ConnectionObserver
|
||||||
from core.Constants import Constants
|
from core.Constants import Constants
|
||||||
from core.errors.logger import logger
|
from core.errors.logger import logger
|
||||||
from core.models.manage.server_reply_tools import format_server_response
|
#from core.models.manage.server_reply_tools import format_server_response
|
||||||
# from core.models.pydantic_models.ServerReply import ServerReply, ServerError
|
# from core.models.pydantic_models.ServerReply import ServerReply, ServerError
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
|
|
||||||
from typing import Union, Optional
|
from typing import Optional
|
||||||
import base64
|
import base64
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
MIN_MTU = 1280
|
||||||
|
|
||||||
def register_wireguard_session(
|
def register_wireguard_session(
|
||||||
profile: Union[SessionProfile, SystemProfile],
|
profile: Profile,
|
||||||
connection_observer: Optional[ConnectionObserver] = None
|
connection_observer: Optional[ConnectionObserver] = None
|
||||||
) -> Result:
|
) -> Result:
|
||||||
"""Register a WireGuard session for the given profile."""
|
"""Register a WireGuard session for the given profile."""
|
||||||
|
|
@ -34,69 +42,40 @@ def register_wireguard_session(
|
||||||
logger.error("No Public Key Generated. Raising ValueError")
|
logger.error("No Public Key Generated. Raising ValueError")
|
||||||
raise ValueError("No Public Key Generated.")
|
raise ValueError("No Public Key Generated.")
|
||||||
|
|
||||||
country_code = profile.location.country_code
|
|
||||||
location_code = profile.location.code
|
|
||||||
billing_code = profile.subscription.billing_code
|
billing_code = profile.subscription.billing_code
|
||||||
url = f'{Constants.SP_API_BASE_URL}/countries/{country_code}/locations/{location_code}/wireguard-sessions'
|
url = f'{Constants.SP_BASE_URL}/v2/wireguard_config'
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
'public_key': public_key
|
'public_key': public_key
|
||||||
}
|
}
|
||||||
|
|
||||||
api_result = connect.single_endpoint(
|
result = api_call_and_extraction(
|
||||||
method="post",
|
desired_type="conf",
|
||||||
|
method=ApiCall.POST,
|
||||||
url=url,
|
url=url,
|
||||||
observer=connection_observer,
|
connection_observer=connection_observer,
|
||||||
payload=payload,
|
payload=payload,
|
||||||
billing_code=billing_code
|
billing_code=billing_code
|
||||||
)
|
)
|
||||||
|
|
||||||
if not api_result.valid:
|
|
||||||
if api_result.error_type == ErrorType.AUTHENTICATION_ERROR:
|
|
||||||
logger.error(f"Server is giving an Authentication error for billing ID: {billing_code}")
|
|
||||||
raise InvalidSubscriptionError(f"Invalid Subscription for {billing_code}")
|
|
||||||
else:
|
|
||||||
error_msg = f"Could Not Connect to API: {api_result.error_type}"
|
|
||||||
logger.error(error_msg)
|
|
||||||
raise ConnectionError(error_msg)
|
|
||||||
|
|
||||||
# 2nd Layer Translation:
|
|
||||||
result = format_server_response(api_response=api_result, desired_type="data")
|
|
||||||
if not result.valid:
|
if not result.valid:
|
||||||
# KILLING THE RESULT FOR NOW. Later, replace this error raise with the return of data,
|
return result
|
||||||
raise InvalidSubscriptionError(result.message)
|
|
||||||
|
|
||||||
wireguard_configuration = result.data
|
wireguard_configuration = result.data
|
||||||
|
|
||||||
if wireguard_configuration is None:
|
if wireguard_configuration is None:
|
||||||
raise InvalidSubscriptionError()
|
return Result(valid=False, error_type=ResultError.SERVER_SIDE, message="Server gave a blank wireguard config.")
|
||||||
|
|
||||||
wireguard_configuration = _inject_private_key(wireguard_configuration, wireguard_keys.get('private'))
|
wireguard_configuration = _inject_private_key(wireguard_configuration, wireguard_keys.get('private'))
|
||||||
|
|
||||||
|
if profile.type == ProfileType.SESSION:
|
||||||
|
logger.info("Injecting MTU..")
|
||||||
|
wireguard_configuration = insert_mtu(mtu=MIN_MTU, wireguard_config=wireguard_configuration)
|
||||||
|
|
||||||
profile.attach_wireguard_configuration(wireguard_configuration)
|
profile.attach_wireguard_configuration(wireguard_configuration)
|
||||||
return Result(valid=True)
|
return Result(valid=True)
|
||||||
|
|
||||||
|
|
||||||
# over-engineering:
|
|
||||||
|
|
||||||
# print(f"wireguard_configuration type is {type(wireguard_configuration)}")
|
|
||||||
# # print(f"wireguard_configuration value is {wireguard_configuration}")
|
|
||||||
|
|
||||||
# if isinstance(wireguard_configuration, dict):
|
|
||||||
# print(f"wireguard_configuration is {type(wireguard_configuration)}")
|
|
||||||
# print(f"wireguard_configuration is {wireguard_configuration}")
|
|
||||||
# logger.error("Server isn't returning a WG config, it's trying to tell you a ServerReply message...")
|
|
||||||
# return format_server_response(wireguard_configuration, "message")
|
|
||||||
|
|
||||||
|
|
||||||
# def handle_config_error(wireguard_configuration: str|dict) -> Result:
|
|
||||||
# """The server gave the wireguard config in an invalid format. First convert it to get the error."""
|
|
||||||
# server_reply = format_server_response(wireguard_configuration, "message")
|
|
||||||
# if server_reply.valid:
|
|
||||||
# return server_reply
|
|
||||||
# else:
|
|
||||||
# return Result(valid=False, error_type=ResultError.NODE_ERROR, message="server-side error with wireguard config format.")
|
|
||||||
|
|
||||||
|
|
||||||
def _generate_wireguard_keys() -> dict:
|
def _generate_wireguard_keys() -> dict:
|
||||||
"""Generate WireGuard public/private key pair."""
|
"""Generate WireGuard public/private key pair."""
|
||||||
from cryptography.hazmat.primitives import serialization
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
|
@ -124,10 +103,84 @@ def _inject_private_key(config: str, private_key: str) -> str:
|
||||||
return re.sub(expression, r'\1 ' + private_key, config)
|
return re.sub(expression, r'\1 ' + private_key, config)
|
||||||
|
|
||||||
|
|
||||||
|
def insert_mtu(mtu: int, wireguard_config: str) -> str:
|
||||||
|
# This regex finds the DNS line and the whitespace following it,
|
||||||
|
# specifically when that whitespace is followed by the [Peer] section.
|
||||||
|
# Group 1: The DNS assignment line
|
||||||
|
# Group 2: The whitespace (including newlines) between DNS and [Peer]
|
||||||
|
# Group 3: The [Peer] header
|
||||||
|
pattern = r"(DNS\s*=\s*[^\n]+)(\s*)(\[Peer\])"
|
||||||
|
|
||||||
|
# We insert the MTU line immediately after the DNS line (\1),
|
||||||
|
# then preserve the original whitespace (\2) and the [Peer] header (\3).
|
||||||
|
replacement = rf"\1\nMTU = {mtu}\2\3"
|
||||||
|
|
||||||
|
return re.sub(pattern, replacement, wireguard_config)
|
||||||
|
|
||||||
|
|
||||||
|
# If need it pretty spacing quickly:
|
||||||
|
def expand_newlines(text):
|
||||||
|
"""
|
||||||
|
Removes leading/trailing whitespace and ensures exactly one
|
||||||
|
empty line between every non-empty line of text.
|
||||||
|
"""
|
||||||
|
return "\n\n".join([line for line in text.splitlines() if line.strip()])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# over-engineering:
|
||||||
|
|
||||||
|
# api_result = connect.single_endpoint(
|
||||||
|
# method="post",
|
||||||
|
# url=url,
|
||||||
|
# observer=connection_observer,
|
||||||
|
# payload=payload,
|
||||||
|
# billing_code=billing_code
|
||||||
|
# )
|
||||||
|
|
||||||
|
# if not api_result.valid:
|
||||||
|
# if api_result.error_type == ErrorType.AUTHENTICATION_ERROR:
|
||||||
|
# logger.error(f"Server is giving an Authentication error for billing ID: {billing_code}")
|
||||||
|
# raise InvalidSubscriptionError(f"Invalid Subscription for {billing_code}")
|
||||||
|
# else:
|
||||||
|
# error_msg = f"Could Not Connect to API: {api_result.error_type}"
|
||||||
|
# logger.error(error_msg)
|
||||||
|
# raise ConnectionError(error_msg)
|
||||||
|
|
||||||
|
# # 2nd Layer Translation:
|
||||||
|
# result = format_server_response(api_response=api_result, desired_type="data")
|
||||||
|
# if not result.valid:
|
||||||
|
# # KILLING THE RESULT FOR NOW. Later, replace this error raise with the return of data,
|
||||||
|
# raise InvalidSubscriptionError(result.message)
|
||||||
|
|
||||||
|
|
||||||
|
# print(f"wireguard_configuration type is {type(wireguard_configuration)}")
|
||||||
|
# # print(f"wireguard_configuration value is {wireguard_configuration}")
|
||||||
|
|
||||||
|
# if isinstance(wireguard_configuration, dict):
|
||||||
|
# print(f"wireguard_configuration is {type(wireguard_configuration)}")
|
||||||
|
# print(f"wireguard_configuration is {wireguard_configuration}")
|
||||||
|
# logger.error("Server isn't returning a WG config, it's trying to tell you a ServerReply message...")
|
||||||
|
# return format_server_response(wireguard_configuration, "message")
|
||||||
|
|
||||||
|
|
||||||
|
# def handle_config_error(wireguard_configuration: str|dict) -> Result:
|
||||||
|
# """The server gave the wireguard config in an invalid format. First convert it to get the error."""
|
||||||
|
# server_reply = format_server_response(wireguard_configuration, "message")
|
||||||
|
# if server_reply.valid:
|
||||||
|
# return server_reply
|
||||||
|
# else:
|
||||||
|
# return Result(valid=False, error_type=ResultError.NODE_ERROR, message="server-side error with wireguard config format.")
|
||||||
|
|
||||||
|
|
||||||
# legacy:
|
# legacy:
|
||||||
|
|
||||||
|
# country_code = profile.location.country_code
|
||||||
|
# location_code = profile.location.code
|
||||||
|
# url = f'{Constants.SP_API_BASE_URL}/countries/{country_code}/locations/{location_code}/wireguard-sessions'
|
||||||
|
|
||||||
# ConnectionController.with_preferred_connection(
|
# ConnectionController.with_preferred_connection(
|
||||||
# profile.location.country_code,
|
# profile.location.country_code,
|
||||||
# profile.location.code,
|
# profile.location.code,
|
||||||
|
|
|
||||||
86
core/services/networking/systemwide/wireguard/mtu_tools.py
Normal file
86
core/services/networking/systemwide/wireguard/mtu_tools.py
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
from core.utils.run_commands import run_generic_command
|
||||||
|
from core.models.Result import Result, ResultError
|
||||||
|
from core.errors.logger import logger
|
||||||
|
|
||||||
|
MAX_MTU = 1500
|
||||||
|
MIN_MTU = 1280
|
||||||
|
# Use integer division for the middle point
|
||||||
|
MIDDLE_MTU = MIN_MTU + ((MAX_MTU - MIN_MTU) // 2)
|
||||||
|
|
||||||
|
# Fallback list for reliability
|
||||||
|
URLS = ["217.156.66.35", "185.165.169.91", "9.9.9.9"]
|
||||||
|
|
||||||
|
def test_an_mtu(mtu_value: int, url: str, timeout: int) -> Result:
|
||||||
|
# Payload size = MTU - 28 bytes (IP + ICMP headers)
|
||||||
|
payload_size = mtu_value - 28
|
||||||
|
if payload_size < 0:
|
||||||
|
return Result(valid=False)
|
||||||
|
|
||||||
|
command = ["ping", "-M", "do", "-s", str(payload_size), "-c", "1", url]
|
||||||
|
human_readable_goal = f"Test the MTU value {mtu_value} against {url}."
|
||||||
|
return run_generic_command(command, human_readable_goal, timeout=timeout)
|
||||||
|
|
||||||
|
def find_optimal_mtu(low: int, high: int, url: str) -> int:
|
||||||
|
"""
|
||||||
|
Performs a binary search between a known working MTU (low)
|
||||||
|
and a known failing MTU (high).
|
||||||
|
"""
|
||||||
|
optimal = low
|
||||||
|
current_timeout = 6
|
||||||
|
|
||||||
|
while low <= high:
|
||||||
|
mid = (low + high) // 2
|
||||||
|
# We increase timeout slightly as we try larger packets
|
||||||
|
# to avoid false negatives due to congestion/latency
|
||||||
|
logger.info(f"Refining: Testing MTU {mid}...")
|
||||||
|
result = test_an_mtu(mid, url, timeout=current_timeout)
|
||||||
|
|
||||||
|
if result.valid:
|
||||||
|
optimal = mid
|
||||||
|
low = mid + 1
|
||||||
|
current_timeout += 1 # Increase timeout as we get closer to max
|
||||||
|
else:
|
||||||
|
high = mid - 1
|
||||||
|
|
||||||
|
return optimal
|
||||||
|
|
||||||
|
def loop_through_testing_mtu():
|
||||||
|
# The sequence to probe the range
|
||||||
|
INITIAL_TEST_LIST = [MAX_MTU, MIDDLE_MTU, MIN_MTU]
|
||||||
|
|
||||||
|
for url in URLS:
|
||||||
|
logger.info(f"Testing connectivity using URL: {url}")
|
||||||
|
|
||||||
|
working_mtu = None
|
||||||
|
failed_above = None
|
||||||
|
|
||||||
|
for each_mtu in INITIAL_TEST_LIST:
|
||||||
|
logger.info(f"Probing MTU {each_mtu}...")
|
||||||
|
result = test_an_mtu(mtu_value=each_mtu, url=url, timeout=6)
|
||||||
|
|
||||||
|
if result.valid:
|
||||||
|
working_mtu = each_mtu
|
||||||
|
# If MAX_MTU works immediately, we've found the ceiling
|
||||||
|
if each_mtu == MAX_MTU:
|
||||||
|
return MAX_MTU
|
||||||
|
break
|
||||||
|
|
||||||
|
if working_mtu:
|
||||||
|
# We found a baseline. Now we need to find the ceiling.
|
||||||
|
# The 'high' is the first value in our test list that failed.
|
||||||
|
# If MIDDLE worked, high is MAX. If MIN worked, high is MIDDLE.
|
||||||
|
if working_mtu == MIDDLE_MTU:
|
||||||
|
failed_above = MAX_MTU
|
||||||
|
elif working_mtu == MIN_MTU:
|
||||||
|
failed_above = MIDDLE_MTU
|
||||||
|
else:
|
||||||
|
failed_above = MAX_MTU + 1
|
||||||
|
|
||||||
|
logger.info(f"Baseline found at {working_mtu}. Searching for optimal...")
|
||||||
|
return find_optimal_mtu(working_mtu, failed_above, url)
|
||||||
|
|
||||||
|
# If we reach here, the MIN_MTU failed for this URL
|
||||||
|
logger.error(f"Could not reach {url} even with minimum MTU. Trying next URL.")
|
||||||
|
|
||||||
|
logger.critical("All URLs failed MTU tests. Check your WireGuard connection.")
|
||||||
|
return None
|
||||||
|
|
@ -185,5 +185,10 @@ def parse_errors(error_output: str) -> ResultError:
|
||||||
elif "command not found" in error_output:
|
elif "command not found" in error_output:
|
||||||
return ResultError.MISSING_DEPENDENCY
|
return ResultError.MISSING_DEPENDENCY
|
||||||
|
|
||||||
|
elif "local error: message too long" in error_output:
|
||||||
|
return ResultError.MTU
|
||||||
|
|
||||||
else:
|
else:
|
||||||
return ResultError.UNKNOWN
|
return ResultError.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue