Skip to content

Commit b851e08

Browse files
authored
Merge pull request #8 from c4g7-dev/copilot/fix-speedtest-ping-issues
Fix gateway detection and loaded ping measurement for VPN scenarios
2 parents bb1656e + b56bb60 commit b851e08

File tree

1 file changed

+79
-10
lines changed

1 file changed

+79
-10
lines changed

app/internal_manager.py

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,9 @@ def run_speedtest_stream(self, device_id: Optional[int] = None) -> Generator[Dic
523523
if local_ping:
524524
results["local_latency_ms"] = local_ping.get("avg_ms")
525525
yield {"event": "metric", "data": {"name": "local_latency", "value": local_ping.get("avg_ms")}}
526+
LOGGER.info(f"Local latency: {local_ping.get('avg_ms')}ms to {local_ping.get('gateway')}")
527+
else:
528+
LOGGER.warning("Local latency measurement returned None - gateway may not be detected or reachable")
526529

527530
yield {"event": "progress", "data": {"percent": 10}}
528531

@@ -532,6 +535,9 @@ def run_speedtest_stream(self, device_id: Optional[int] = None) -> Generator[Dic
532535
if gateway_ping:
533536
results["gateway_ping_ms"] = gateway_ping.get("avg_ms")
534537
yield {"event": "metric", "data": {"name": "gateway_ping", "value": gateway_ping.get("avg_ms")}}
538+
LOGGER.info(f"Gateway ping: {gateway_ping.get('avg_ms')}ms to {gateway_ping.get('gateway')}")
539+
else:
540+
LOGGER.warning("Gateway ping measurement returned None - gateway may not be reachable")
535541

536542
yield {"event": "progress", "data": {"percent": 15}}
537543

@@ -636,6 +642,8 @@ def measure_continuous_ping():
636642
results["ping_during_download_ms"] = round(avg_loaded, 2)
637643
# Also set upload to same value since we measured continuously
638644
results["ping_during_upload_ms"] = round(avg_loaded, 2)
645+
# Set the unified ping_loaded_ms field for storage and display
646+
results["ping_loaded_ms"] = round(avg_loaded, 2)
639647
yield {"event": "metric", "data": {"name": "ping_loaded", "value": results["ping_during_download_ms"]}}
640648

641649
# Phase 5: Calculate grade based on bufferbloat
@@ -702,7 +710,8 @@ def _measure_local_latency(self) -> Optional[Dict[str, Any]]:
702710
# Get gateway IP
703711
gateway = self._get_default_gateway()
704712
if not gateway:
705-
gateway = "127.0.0.1"
713+
LOGGER.warning("No gateway found for local latency measurement")
714+
return None
706715

707716
try:
708717
if platform.system() == "Windows":
@@ -730,7 +739,10 @@ def _measure_local_latency(self) -> Optional[Dict[str, Any]]:
730739
diffs = [abs(times[i+1] - times[i]) for i in range(len(times)-1)]
731740
jitter_ms = sum(diffs) / len(diffs)
732741

742+
LOGGER.info(f"Local latency to {gateway}: {avg_ms:.2f}ms")
733743
return {"avg_ms": avg_ms, "jitter_ms": jitter_ms, "gateway": gateway}
744+
else:
745+
LOGGER.warning(f"No ping responses from gateway {gateway}")
734746

735747
except Exception as e:
736748
LOGGER.warning(f"Local latency measurement failed: {e}")
@@ -772,32 +784,89 @@ def _measure_gateway_ping(self) -> Optional[Dict[str, Any]]:
772784
return None
773785

774786
def _get_default_gateway(self) -> Optional[str]:
775-
"""Get the default gateway IP."""
787+
"""Get the default gateway IP.
788+
789+
Tries multiple methods to find the LAN gateway, especially useful
790+
when connected via VPN where the default route points to VPN gateway.
791+
"""
792+
gateways = []
793+
776794
try:
777795
if platform.system() == "Windows":
778796
result = subprocess.run(
779797
["route", "print", "0.0.0.0"],
780798
capture_output=True, text=True, timeout=10
781799
)
782-
# Parse Windows route table
800+
# Parse Windows route table - collect all potential gateways
783801
for line in result.stdout.split('\n'):
784802
if "0.0.0.0" in line:
785803
parts = line.split()
786804
for part in parts:
787805
if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', part):
788-
if part != "0.0.0.0":
789-
return part
806+
if part != "0.0.0.0" and not part.startswith("255."):
807+
gateways.append(part)
790808
else:
809+
# Try default route first
791810
result = subprocess.run(
792811
["ip", "route", "show", "default"],
793812
capture_output=True, text=True, timeout=10
794813
)
795-
match = re.search(r'via\s+(\d+\.\d+\.\d+\.\d+)', result.stdout)
796-
if match:
797-
return match.group(1)
798-
except Exception:
799-
pass
814+
for match in re.finditer(r'via\s+(\d+\.\d+\.\d+\.\d+)', result.stdout):
815+
gateways.append(match.group(1))
816+
817+
# Also check for LAN routes (192.168.x.x, 10.x.x.x, 172.16-31.x.x)
818+
# This helps when VPN has taken over the default route
819+
result = subprocess.run(
820+
["ip", "route", "show"],
821+
capture_output=True, text=True, timeout=10
822+
)
823+
for line in result.stdout.split('\n'):
824+
# Look for routes to private networks (RFC 1918)
825+
match = re.search(r'via\s+(\d+\.\d+\.\d+\.\d+)', line)
826+
if match:
827+
gateway = match.group(1)
828+
# Check if gateway is in private IP ranges
829+
if (gateway.startswith('192.168.') or
830+
gateway.startswith('10.') or
831+
(gateway.startswith('172.') and '.' in gateway)):
832+
# Validate 172.16-31.x.x range
833+
parts = gateway.split('.')
834+
if len(parts) >= 2:
835+
try:
836+
second_octet = int(parts[1])
837+
if gateway.startswith('172.') and not (16 <= second_octet <= 31):
838+
continue # Not in RFC 1918 range
839+
except (ValueError, IndexError):
840+
continue # Malformed IP
841+
if gateway not in gateways:
842+
gateways.append(gateway)
843+
except Exception as e:
844+
LOGGER.debug(f"Gateway detection failed: {e}")
845+
846+
# Prefer private network gateways over VPN gateways
847+
# Check if any gateway is in common private ranges (more likely to be LAN)
848+
for gateway in gateways:
849+
try:
850+
parts = gateway.split('.')
851+
if len(parts) != 4:
852+
continue
853+
if gateway.startswith('192.168.') or gateway.startswith('10.'):
854+
LOGGER.info(f"Selected LAN gateway: {gateway}")
855+
return gateway
856+
if gateway.startswith('172.'):
857+
second_octet = int(parts[1])
858+
if 16 <= second_octet <= 31:
859+
LOGGER.info(f"Selected LAN gateway: {gateway}")
860+
return gateway
861+
except (ValueError, IndexError):
862+
continue # Skip malformed IPs
863+
864+
# Fall back to first gateway found
865+
if gateways:
866+
LOGGER.info(f"Selected gateway: {gateways[0]}")
867+
return gateways[0]
800868

869+
LOGGER.warning("No gateway found")
801870
return None
802871

803872
def _measure_ping_async(self, target: str = "8.8.8.8", count: int = 5) -> Dict[str, Any]:

0 commit comments

Comments
 (0)