mirror of
				https://github.com/2dust/v2rayN.git
				synced 2025-10-31 20:42:52 +00:00 
			
		
		
		
	Refactor Linux to run Tun as sudo
This commit is contained in:
		
							parent
							
								
									0032a3d27a
								
							
						
					
					
						commit
						adf3b955d6
					
				
					 6 changed files with 171 additions and 124 deletions
				
			
		|  | @ -31,15 +31,6 @@ public sealed class AppHandler | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public bool IsAdministrator | ||||
|     { | ||||
|         get | ||||
|         { | ||||
|             _isAdministrator ??= Utils.IsAdministrator(); | ||||
|             return _isAdministrator.Value; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public string LinuxSudoPwd { get; set; } | ||||
| 
 | ||||
|     #endregion Property | ||||
|  |  | |||
							
								
								
									
										144
									
								
								v2rayN/ServiceLib/Handler/CoreAdminHandler.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								v2rayN/ServiceLib/Handler/CoreAdminHandler.cs
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,144 @@ | |||
| using System.Diagnostics; | ||||
| using System.Text; | ||||
| using CliWrap; | ||||
| 
 | ||||
| namespace ServiceLib.Handler; | ||||
| 
 | ||||
| public class CoreAdminHandler | ||||
| { | ||||
|     private static readonly Lazy<CoreAdminHandler> _instance = new(() => new()); | ||||
|     public static CoreAdminHandler Instance => _instance.Value; | ||||
|     private Config _config; | ||||
|     private Action<bool, string>? _updateFunc; | ||||
|     private const string _tag = "CoreAdminHandler"; | ||||
|     private int _linuxSudoPid = -1; | ||||
| 
 | ||||
|     public async Task Init(Config config, Action<bool, string> updateFunc) | ||||
|     { | ||||
|         if (_config != null) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
|         _config = config; | ||||
|         _updateFunc = updateFunc; | ||||
|     } | ||||
| 
 | ||||
|     private void UpdateFunc(bool notify, string msg) | ||||
|     { | ||||
|         _updateFunc?.Invoke(notify, msg); | ||||
|     } | ||||
| 
 | ||||
|     public async Task<Process?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}"; | ||||
|             var shFilePath = await CreateLinuxShellFile(cmdLine, "run_as_sudo.sh"); | ||||
| 
 | ||||
|             Process proc = new() | ||||
|             { | ||||
|                 StartInfo = new() | ||||
|                 { | ||||
|                     FileName = shFilePath, | ||||
|                     Arguments = "", | ||||
|                     WorkingDirectory = Utils.GetBinConfigPath(), | ||||
|                     UseShellExecute = false, | ||||
|                     RedirectStandardInput = true, | ||||
|                     RedirectStandardOutput = true, | ||||
|                     RedirectStandardError = true, | ||||
|                     CreateNoWindow = true, | ||||
|                     StandardInputEncoding = Encoding.UTF8, | ||||
|                     StandardOutputEncoding = Encoding.UTF8, | ||||
|                     StandardErrorEncoding = Encoding.UTF8, | ||||
|                 } | ||||
|             }; | ||||
| 
 | ||||
|             proc.OutputDataReceived += (sender, e) => | ||||
|             { | ||||
|                 if (e.Data.IsNotEmpty()) | ||||
|                 { | ||||
|                     UpdateFunc(false, e.Data + Environment.NewLine); | ||||
|                 } | ||||
|             }; | ||||
|             proc.ErrorDataReceived += (sender, e) => | ||||
|             { | ||||
|                 if (e.Data.IsNotEmpty()) | ||||
|                 { | ||||
|                     UpdateFunc(false, e.Data + Environment.NewLine); | ||||
|                 } | ||||
|             }; | ||||
| 
 | ||||
|             proc.Start(); | ||||
|             proc.BeginOutputReadLine(); | ||||
|             proc.BeginErrorReadLine(); | ||||
| 
 | ||||
|             await Task.Delay(10); | ||||
|             await proc.StandardInput.WriteLineAsync(); | ||||
|             await Task.Delay(10); | ||||
|             await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd); | ||||
| 
 | ||||
|             await Task.Delay(100); | ||||
|             if (proc is null or { HasExited: true }) | ||||
|             { | ||||
|                 throw new Exception(ResUI.FailedToRunCore); | ||||
|             } | ||||
| 
 | ||||
|             _linuxSudoPid = proc.Id; | ||||
| 
 | ||||
|             return proc; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             Logging.SaveLog(_tag, ex); | ||||
|             UpdateFunc(false, ex.Message); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public async Task KillProcessAsLinuxSudo() | ||||
|     { | ||||
|         if (_linuxSudoPid < 0) | ||||
|         { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         try | ||||
|         { | ||||
|             var cmdLine = $"pkill -P {_linuxSudoPid} ; kill {_linuxSudoPid}"; | ||||
|             var shFilePath = await CreateLinuxShellFile(cmdLine, "kill_as_sudo.sh"); | ||||
| 
 | ||||
|             var result = await Cli.Wrap(shFilePath) | ||||
|                 .WithStandardInputPipe(PipeSource.FromString(AppHandler.Instance.LinuxSudoPwd)) | ||||
|                 .ExecuteAsync(); | ||||
| 
 | ||||
|             _linuxSudoPid = -1; | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             Logging.SaveLog(_tag, ex); | ||||
|             UpdateFunc(false, ex.Message); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private async Task<string> CreateLinuxShellFile(string cmdLine, string fileName) | ||||
|     { | ||||
|         var shFilePath = Utils.GetBinConfigPath(fileName); | ||||
|         File.Delete(shFilePath); | ||||
| 
 | ||||
|         var sb = new StringBuilder(); | ||||
|         sb.AppendLine("#!/bin/sh"); | ||||
|         if (Utils.IsAdministrator()) | ||||
|         { | ||||
|             sb.AppendLine($"{cmdLine}"); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             sb.AppendLine($"sudo -S {cmdLine}"); | ||||
|         } | ||||
| 
 | ||||
|         await File.WriteAllTextAsync(shFilePath, sb.ToString()); | ||||
|         await Utils.SetLinuxChmod(shFilePath); | ||||
| 
 | ||||
|         return shFilePath; | ||||
|     } | ||||
| } | ||||
|  | @ -13,7 +13,7 @@ public class CoreHandler | |||
|     private Config _config; | ||||
|     private Process? _process; | ||||
|     private Process? _processPre; | ||||
|     private int _linuxSudoPid = -1; | ||||
|     private bool _linuxSudo = false; | ||||
|     private Action<bool, string>? _updateFunc; | ||||
|     private const string _tag = "CoreHandler"; | ||||
| 
 | ||||
|  | @ -155,21 +155,21 @@ public class CoreHandler | |||
|     { | ||||
|         try | ||||
|         { | ||||
|             if (_linuxSudoPid > 0) | ||||
|             if (_linuxSudo) | ||||
|             { | ||||
|                 await KillProcessAsLinuxSudo(); | ||||
|                 _linuxSudoPid = -1; | ||||
|                 await CoreAdminHandler.Instance.KillProcessAsLinuxSudo(); | ||||
|                 _linuxSudo = false; | ||||
|             } | ||||
| 
 | ||||
|             if (_process != null) | ||||
|             { | ||||
|                 await ProcUtils.ProcessKill(_process, true); | ||||
|                 await ProcUtils.ProcessKill(_process, Utils.IsWindows()); | ||||
|                 _process = null; | ||||
|             } | ||||
| 
 | ||||
|             if (_processPre != null) | ||||
|             { | ||||
|                 await ProcUtils.ProcessKill(_processPre, true); | ||||
|                 await ProcUtils.ProcessKill(_processPre, Utils.IsWindows()); | ||||
|                 _processPre = null; | ||||
|             } | ||||
|         } | ||||
|  | @ -225,13 +225,6 @@ public class CoreHandler | |||
|         _updateFunc?.Invoke(notify, msg); | ||||
|     } | ||||
| 
 | ||||
|     private bool IsNeedSudo(ECoreType eCoreType) | ||||
|     { | ||||
|         return _config.TunModeItem.EnableTun | ||||
|                && eCoreType == ECoreType.sing_box | ||||
|                && Utils.IsNonWindows(); | ||||
|     } | ||||
| 
 | ||||
|     #endregion Private | ||||
| 
 | ||||
|     #region Process | ||||
|  | @ -245,6 +238,16 @@ public class CoreHandler | |||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         if (mayNeedSudo | ||||
|             && _config.TunModeItem.EnableTun | ||||
|             && coreInfo.CoreType == ECoreType.sing_box | ||||
|             && Utils.IsNonWindows()) | ||||
|         { | ||||
|             _linuxSudo = true; | ||||
|             await CoreAdminHandler.Instance.Init(_config, _updateFunc); | ||||
|             return await CoreAdminHandler.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath); | ||||
|         } | ||||
| 
 | ||||
|         try | ||||
|         { | ||||
|             Process proc = new() | ||||
|  | @ -263,38 +266,25 @@ public class CoreHandler | |||
|                 } | ||||
|             }; | ||||
| 
 | ||||
|             var isNeedSudo = mayNeedSudo && IsNeedSudo(coreInfo.CoreType); | ||||
|             if (isNeedSudo) | ||||
|             { | ||||
|                 await RunProcessAsLinuxSudo(proc, fileName, coreInfo, configPath); | ||||
|             } | ||||
| 
 | ||||
|             if (displayLog) | ||||
|             { | ||||
|                 proc.OutputDataReceived += (sender, e) => | ||||
|                 { | ||||
|                     if (e.Data.IsNullOrEmpty()) | ||||
|                         return; | ||||
|                     UpdateFunc(false, e.Data + Environment.NewLine); | ||||
|                     if (e.Data.IsNotEmpty()) | ||||
|                     { | ||||
|                         UpdateFunc(false, e.Data + Environment.NewLine); | ||||
|                     } | ||||
|                 }; | ||||
|                 proc.ErrorDataReceived += (sender, e) => | ||||
|                 { | ||||
|                     if (e.Data.IsNullOrEmpty()) | ||||
|                         return; | ||||
|                     UpdateFunc(false, e.Data + Environment.NewLine); | ||||
|                     if (e.Data.IsNotEmpty()) | ||||
|                     { | ||||
|                         UpdateFunc(false, e.Data + Environment.NewLine); | ||||
|                     } | ||||
|                 }; | ||||
|             } | ||||
|             proc.Start(); | ||||
| 
 | ||||
|             if (isNeedSudo && AppHandler.Instance.LinuxSudoPwd.IsNotEmpty()) | ||||
|             { | ||||
|                 await proc.StandardInput.WriteLineAsync(); | ||||
|                 await Task.Delay(10); | ||||
|                 await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd); | ||||
|             } | ||||
|             if (isNeedSudo) | ||||
|                 _linuxSudoPid = proc.Id; | ||||
| 
 | ||||
|             if (displayLog) | ||||
|             { | ||||
|                 proc.BeginOutputReadLine(); | ||||
|  | @ -318,82 +308,4 @@ public class CoreHandler | |||
|     } | ||||
| 
 | ||||
|     #endregion Process | ||||
| 
 | ||||
|     #region Linux | ||||
| 
 | ||||
|     private async Task RunProcessAsLinuxSudo(Process proc, string fileName, CoreInfo coreInfo, string configPath) | ||||
|     { | ||||
|         var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}"; | ||||
| 
 | ||||
|         var shFilePath = await CreateLinuxShellFile(cmdLine, "run_as_sudo.sh"); | ||||
|         proc.StartInfo.FileName = shFilePath; | ||||
|         proc.StartInfo.Arguments = ""; | ||||
|         proc.StartInfo.WorkingDirectory = ""; | ||||
|         if (AppHandler.Instance.LinuxSudoPwd.IsNotEmpty()) | ||||
|         { | ||||
|             proc.StartInfo.StandardInputEncoding = Encoding.UTF8; | ||||
|             proc.StartInfo.RedirectStandardInput = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private async Task KillProcessAsLinuxSudo() | ||||
|     { | ||||
|         var cmdLine = $"pkill -P {_linuxSudoPid} ; kill {_linuxSudoPid}"; | ||||
|         var shFilePath = await CreateLinuxShellFile(cmdLine, "kill_as_sudo.sh"); | ||||
|         Process proc = new() | ||||
|         { | ||||
|             StartInfo = new() | ||||
|             { | ||||
|                 FileName = shFilePath, | ||||
|                 UseShellExecute = false, | ||||
|                 CreateNoWindow = true, | ||||
|                 StandardInputEncoding = Encoding.UTF8, | ||||
|                 RedirectStandardInput = true | ||||
|             } | ||||
|         }; | ||||
|         proc.Start(); | ||||
| 
 | ||||
|         if (AppHandler.Instance.LinuxSudoPwd.IsNotEmpty()) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 await proc.StandardInput.WriteLineAsync(); | ||||
|                 await Task.Delay(10); | ||||
|                 await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd); | ||||
|             } | ||||
|             catch (Exception) | ||||
|             { | ||||
|                 // ignored | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)); | ||||
|         await proc.WaitForExitAsync(timeout.Token); | ||||
|         await Task.Delay(1000); | ||||
|     } | ||||
| 
 | ||||
|     private async Task<string> CreateLinuxShellFile(string cmdLine, string fileName) | ||||
|     { | ||||
|         //Shell scripts | ||||
|         var shFilePath = Utils.GetBinConfigPath(AppHandler.Instance.IsAdministrator ? "root_" + fileName : fileName); | ||||
|         File.Delete(shFilePath); | ||||
|         var sb = new StringBuilder(); | ||||
|         sb.AppendLine("#!/bin/sh"); | ||||
|         if (AppHandler.Instance.IsAdministrator) | ||||
|         { | ||||
|             sb.AppendLine($"{cmdLine}"); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             sb.AppendLine($"sudo -S {cmdLine}"); | ||||
|         } | ||||
| 
 | ||||
|         await File.WriteAllTextAsync(shFilePath, sb.ToString()); | ||||
|         await Utils.SetLinuxChmod(shFilePath); | ||||
|         //Logging.SaveLog(shFilePath); | ||||
| 
 | ||||
|         return shFilePath; | ||||
|     } | ||||
| 
 | ||||
|     #endregion Linux | ||||
| } | ||||
|  |  | |||
|  | @ -454,7 +454,7 @@ public class StatusBarViewModel : MyReactiveObject | |||
|     { | ||||
|         if (Utils.IsWindows()) | ||||
|         { | ||||
|             return AppHandler.Instance.IsAdministrator; | ||||
|             return Utils.IsAdministrator(); | ||||
|         } | ||||
|         else if (Utils.IsLinux()) | ||||
|         { | ||||
|  |  | |||
|  | @ -143,7 +143,7 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel> | |||
|         } | ||||
|         else | ||||
|         { | ||||
|             if (AppHandler.Instance.IsAdministrator) | ||||
|             if (Utils.IsAdministrator()) | ||||
|             { | ||||
|                 this.Title = $"{Utils.GetVersion()} - {ResUI.TbSettingsLinuxSudoPasswordNotSudoRunApp}"; | ||||
|                 NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.TbSettingsLinuxSudoPasswordNotSudoRunApp); | ||||
|  |  | |||
|  | @ -132,7 +132,7 @@ public partial class MainWindow | |||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         this.Title = $"{Utils.GetVersion()} - {(AppHandler.Instance.IsAdministrator ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}"; | ||||
|         this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}"; | ||||
| 
 | ||||
|         if (!_config.GuiItem.EnableHWA) | ||||
|         { | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 2dust
						2dust