using System.Net; using System.Net.NetworkInformation; using System.Text; using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// L16-1: ping·tracert 빠른 실행 핸들러. "ping" 프리픽스로 사용합니다. /// /// 예: ping 8.8.8.8 → ping 결과 (4회) /// ping google.com → 도메인 ping /// ping trace 8.8.8.8 → tracert 실행 /// ping local → 로컬 네트워크 어댑터 정보 /// ping scan 192.168.1.0 → 간단 네트워크 스캔 (1~254) /// Enter → 결과 복사 또는 외부 터미널 실행. /// 사내 모드: 외부 IP/도메인 ping 차단. /// public class PingHandler : IActionHandler { public string? Prefix => "ping"; public PluginMetadata Metadata => new( "Ping", "ping·tracert 빠른 실행 — 네트워크 연결 확인·경로 추적", "1.0", "AX"); private static readonly string[] QuickTargets = ["localhost", "8.8.8.8", "1.1.1.1", "google.com", "192.168.1.1"]; public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); var items = new List(); if (string.IsNullOrWhiteSpace(q)) { items.Add(new LauncherItem("ping / tracert 실행기", "예: ping 8.8.8.8 / ping trace 192.168.1.1 / ping local / ping scan 192.168.1", null, null, Symbol: "\uE968")); items.Add(new LauncherItem("── 빠른 대상 ──", "", null, null, Symbol: "\uE968")); foreach (var t in QuickTargets) items.Add(new LauncherItem($"ping {t}", t, null, ("ping", t), Symbol: "\uE968")); return Task.FromResult>(items); } var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries); var sub = parts[0].ToLowerInvariant(); // local → 로컬 어댑터 정보 if (sub == "local" || sub == "lo") { items.AddRange(BuildLocalNetworkItems()); return Task.FromResult>(items); } // trace / tracert / traceroute if (sub is "trace" or "tracert" or "traceroute") { var target = parts.Length > 1 ? parts[1] : ""; if (string.IsNullOrWhiteSpace(target)) { items.Add(new LauncherItem("대상 주소 입력", "예: ping trace 8.8.8.8", null, null, Symbol: "\uE783")); } else { var blocked = CheckInternalMode(target); if (blocked != null) { items.Add(blocked); return Task.FromResult>(items); } items.Add(new LauncherItem($"tracert {target}", "Enter → 터미널에서 tracert 실행", null, ("tracert", target), Symbol: "\uE968")); items.Add(new LauncherItem("터미널 실행", $"tracert {target}", null, ("tracert", target), Symbol: "\uE968")); } return Task.FromResult>(items); } // scan → 간단 스캔 (비동기 결과는 실행 시 터미널) if (sub == "scan") { var network = parts.Length > 1 ? parts[1] : "192.168.1"; items.Add(new LauncherItem($"네트워크 스캔: {network}.1~254", "Enter → 터미널에서 ping 스캔 스크립트 실행", null, ("scan", network), Symbol: "\uE968")); items.Add(new LauncherItem("팁", "결과가 많을 수 있습니다 — 터미널에서 확인하세요", null, null, Symbol: "\uE946")); return Task.FromResult>(items); } // 직접 ping 대상 var host = parts[0]; var blocked2 = CheckInternalMode(host); if (blocked2 != null) { items.Add(blocked2); return Task.FromResult>(items); } items.Add(new LauncherItem($"ping {host}", "Enter → 비동기 ping (4회) 실행", null, ("ping", host), Symbol: "\uE968")); items.Add(new LauncherItem($"tracert {host}", "Enter → 터미널에서 tracert 실행", null, ("tracert", host), Symbol: "\uE968")); items.Add(new LauncherItem($"ping 연속 {host}", "ping -t (무한 반복) — 터미널", null, ("ping_t", host), Symbol: "\uE968")); // 즉시 1회 ping 시도 var pingResult = TryPingOnce(host); if (pingResult != null) { items.Insert(0, pingResult); } return Task.FromResult>(items); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { switch (item.Data) { case ("ping", string host): RunInTerminal($"ping {host}"); break; case ("ping_t", string host): RunInTerminal($"ping -t {host}"); break; case ("tracert", string host): RunInTerminal($"tracert {host}"); break; case ("scan", string network): // PowerShell로 간단 스캔 var ps = $"1..254 | ForEach-Object {{ $ip = '{network}.$_'; if (Test-Connection $ip -Count 1 -Quiet -TimeoutSeconds 1) {{ Write-Host \"$ip is UP\" }} }}; Read-Host 'Press Enter'"; RunInTerminal($"powershell -NoExit -Command \"{ps}\"", usePs: true); break; case ("copy", string text): try { System.Windows.Application.Current.Dispatcher.Invoke( () => Clipboard.SetText(text)); NotificationService.Notify("Ping", "복사됨"); } catch { /* 비핵심 */ } break; } return Task.CompletedTask; } // ── 헬퍼 ───────────────────────────────────────────────────────────────── private static List BuildLocalNetworkItems() { var items = new List(); try { var ifaces = NetworkInterface.GetAllNetworkInterfaces() .Where(n => n.OperationalStatus == OperationalStatus.Up && n.NetworkInterfaceType != NetworkInterfaceType.Loopback) .ToList(); items.Add(new LauncherItem($"로컬 네트워크 어댑터 {ifaces.Count}개", "", null, null, Symbol: "\uE968")); foreach (var iface in ifaces) { var ipProps = iface.GetIPProperties(); var ipv4 = ipProps.UnicastAddresses .FirstOrDefault(a => a.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork); var gateway = ipProps.GatewayAddresses.FirstOrDefault()?.Address?.ToString() ?? "없음"; if (ipv4 == null) continue; var ip = ipv4.Address.ToString(); var mask = ipv4.IPv4Mask.ToString(); var label = $"{iface.Name} {ip}"; var sub2 = $"넷마스크 {mask} · 게이트웨이 {gateway}"; items.Add(new LauncherItem(label, sub2, null, ("copy", ip), Symbol: "\uE968")); } // 외부 IP 안내 (사외 모드에서만) var settings = (System.Windows.Application.Current as App)?.SettingsService?.Settings; if (settings?.InternalModeEnabled == false) items.Add(new LauncherItem("외부 IP 조회", "ping trace 8.8.8.8 으로 경로 확인", null, null, Symbol: "\uE968")); } catch (Exception ex) { items.Add(new LauncherItem("네트워크 정보 조회 오류", ex.Message, null, null, Symbol: "\uE783")); } return items; } private static LauncherItem? TryPingOnce(string host) { try { using var p = new Ping(); var reply = p.Send(host, 1000); if (reply.Status == IPStatus.Success) { var label = $"✓ 응답 {reply.RoundtripTime}ms"; return new LauncherItem(label, $"TTL {reply.Options?.Ttl ?? 0} · {host}", null, ("copy", $"{reply.RoundtripTime}ms"), Symbol: "\uE968"); } return new LauncherItem($"✗ 응답 없음 ({reply.Status})", host, null, null, Symbol: "\uE783"); } catch { return null; // 오류 시 무시 } } private static LauncherItem? CheckInternalMode(string host) { var settings = (System.Windows.Application.Current as App)?.SettingsService?.Settings; if (settings?.InternalModeEnabled != true) return null; // 내부 주소는 허용 if (host.StartsWith("192.168.", StringComparison.Ordinal) || host.StartsWith("10.", StringComparison.Ordinal) || host.StartsWith("172.", StringComparison.Ordinal) || host.Equals("localhost", StringComparison.OrdinalIgnoreCase) || host.StartsWith("127.", StringComparison.Ordinal)) return null; if (IPAddress.TryParse(host, out _)) return null; // IP 주소 → 내부로 간주 허용 return new LauncherItem("사내 모드 — 외부 도메인 차단", "사외 모드에서 외부 주소 ping 가능. 설정에서 변경하세요.", null, null, Symbol: "\uE783"); } private static void RunInTerminal(string cmd, bool usePs = false) { try { var wtPath = FindExe("wt.exe"); if (wtPath != null && !usePs) { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = wtPath, Arguments = $"cmd /K {cmd}", UseShellExecute = false, }); } else { System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = usePs ? "powershell" : "cmd", Arguments = usePs ? $"-NoExit -Command \"{cmd}\"" : $"/K {cmd}", UseShellExecute = true, }); } } catch { /* 비핵심 */ } } private static string? FindExe(string name) { foreach (var dir in (Environment.GetEnvironmentVariable("PATH") ?? "").Split(';')) { var full = System.IO.Path.Combine(dir.Trim(), name); if (System.IO.File.Exists(full)) return full; } return null; } }