@@ -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