@@ -1448,9 +1448,104 @@ def stop(self) -> None:
14481448 level = "warning" ,
14491449 )
14501450
1451- # 4) Emit final state
1451+ # 4) As a final safety net, kill any process using our bundled
1452+ # OpenVPN binary on the current platform.
1453+ try :
1454+ self ._kill_all_bundled_openvpn ()
1455+ except Exception as e :
1456+ self ._logger .append (
1457+ f"[OpenVPN] Final bundled-binary kill failed: { e } " ,
1458+ channel = self ._log_channel ,
1459+ level = "warning" ,
1460+ )
1461+
1462+ # 5) Emit final state
14521463 self .stateChanged .emit ("stopped" )
14531464
1465+ def _kill_all_bundled_openvpn (self ) -> None :
1466+ """
1467+ Final safety net: kill all processes that are using our bundled
1468+ OpenVPN binary. This helps in cases where multiple actions
1469+ (diagnostic, RDP, etc.) were started in quick succession and
1470+ some child OpenVPN processes are still running.
1471+ """
1472+ try :
1473+ bin_path = self ._binary_path ()
1474+ except Exception as e :
1475+ self ._logger .append (
1476+ f"[OpenVPN] _kill_all_bundled_openvpn(): failed to resolve binary path: { e } " ,
1477+ channel = self ._log_channel ,
1478+ level = "warning" ,
1479+ )
1480+ return
1481+
1482+ if not bin_path or not os .path .isabs (bin_path ) or not os .path .exists (bin_path ):
1483+ self ._logger .append (
1484+ f"[OpenVPN] _kill_all_bundled_openvpn(): binary path not suitable: { bin_path !r} " ,
1485+ channel = self ._log_channel ,
1486+ level = "debug" ,
1487+ )
1488+ return
1489+
1490+ osname = self ._helper .get_os ()
1491+ self ._logger .append (
1492+ f"[OpenVPN] _kill_all_bundled_openvpn(): attempting pkill for { bin_path } on { osname } " ,
1493+ channel = self ._log_channel ,
1494+ level = "debug" ,
1495+ )
1496+
1497+ try :
1498+ if osname == "macos" :
1499+ # Use AppleScript so we can kill root-owned processes too.
1500+ kill_cmd = f"pkill -f { bin_path } "
1501+ as_cmd = kill_cmd .replace ("\\ " , "\\ \\ " ).replace ('"' , '\\ "' )
1502+ applescript = f'do shell script "{ as_cmd } " with administrator privileges'
1503+ proc = subprocess .Popen (
1504+ ["osascript" , "-e" , applescript ],
1505+ stdout = subprocess .PIPE ,
1506+ stderr = subprocess .PIPE ,
1507+ text = True ,
1508+ )
1509+ try :
1510+ out , err = proc .communicate (timeout = 10 )
1511+ except Exception :
1512+ out , err = "" , ""
1513+ self ._logger .append (
1514+ f"[OpenVPN] _kill_all_bundled_openvpn() macOS pkill result: "
1515+ f"returncode={ proc .returncode } , stdout={ out !r} , stderr={ err !r} " ,
1516+ channel = self ._log_channel ,
1517+ level = "debug" ,
1518+ )
1519+ else :
1520+ # On Linux and other POSIX platforms, we can typically pkill
1521+ # directly as the same user.
1522+ try :
1523+ proc = subprocess .run (
1524+ ["pkill" , "-f" , bin_path ],
1525+ check = False ,
1526+ stdout = subprocess .PIPE ,
1527+ stderr = subprocess .PIPE ,
1528+ text = True ,
1529+ )
1530+ self ._logger .append (
1531+ f"[OpenVPN] _kill_all_bundled_openvpn() pkill result: "
1532+ f"returncode={ proc .returncode } , stdout={ proc .stdout !r} , stderr={ proc .stderr !r} " ,
1533+ channel = self ._log_channel ,
1534+ level = "debug" ,
1535+ )
1536+ except FileNotFoundError :
1537+ self ._logger .append (
1538+ "[OpenVPN] _kill_all_bundled_openvpn(): pkill not found; skipping." ,
1539+ channel = self ._log_channel ,
1540+ level = "info" ,
1541+ )
1542+ except Exception as e :
1543+ self ._logger .append (
1544+ f"[OpenVPN] _kill_all_bundled_openvpn(): unexpected error: { e } " ,
1545+ channel = self ._log_channel ,
1546+ level = "warning" ,
1547+ )
1548+
14541549 # ------------------------------------------------------------------
14551550 # Command generation
14561551 # ------------------------------------------------------------------
0 commit comments