commit bada4c5a25f2f6120286c16748e4cda8cbc306cd Author: SashegDev Date: Wed Jun 17 18:39:59 2026 +0000 feat: первая версия Just a Blocker WPF приложение для блокировки входящих/исходящих соединений через Windows Filtering Platform. Работает в фоне (трей), автозапуск через реестр. Фильтры персистентные — остаются активными даже после выхода из приложения. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e114d14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +## .NET +bin/ +obj/ +*.user +*.suo +*.sln.docstates +.vs/ + +## Build +packages/ +*.nupkg + +## IDE +.idea/ +*.swp +*~ + +## OS +.DS_Store +Thumbs.db diff --git a/JustABlocker.sln b/JustABlocker.sln new file mode 100644 index 0000000..9e9f873 --- /dev/null +++ b/JustABlocker.sln @@ -0,0 +1,19 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JustABlocker", "src\JustABlocker\JustABlocker.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/README.md b/README.md new file mode 100644 index 0000000..1fffef2 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Just a Blocker + +Блокирует входящие/исходящие соединения на указанные домены и IP-адреса. + +Работает через **Windows Filtering Platform (WFP)** — фильтрует трафик на уровне ядра, блокирует любые протоколы (TCP, UDP, ICMP и т.д.). + +## Как это работает + +1. Добавляешь домен или IP в список +2. Приложение резолвит домен в IP-адреса (IPv4 + IPv6) +3. Создаёт WFP-фильтры на всех сетевых интерфейсах +4. Все пакеты к этим IP дропаются на уровне ядра + +Фильтры **персистентные** — переживают перезагрузку и продолжают работать, даже если приложение не запущено. + +## Использование + +- Добавить в список: `example.com` или `1.2.3.4` +- Удалить из списка — блокировка прекращается +- Pause — временно отключает все фильтры +- Приложение сворачивается в трей + +## Сборка + +```bash +dotnet publish src/JustABlocker/JustABlocker.csproj \ + -c Release -r win-x64 --self-contained true \ + -p:IncludeNativeLibrariesForSelfExtract=true + +# EXE: src/JustABlocker/bin/Release/net8.0-windows/win-x64/publish/JustABlocker.exe +``` + +## Системные требования + +- Windows 7+ +- .NET 8 (self-contained — не нужен) +- Права администратора (WFP требует Admin) diff --git a/src/JustABlocker/App.xaml b/src/JustABlocker/App.xaml new file mode 100644 index 0000000..64e85a9 --- /dev/null +++ b/src/JustABlocker/App.xaml @@ -0,0 +1,5 @@ + + diff --git a/src/JustABlocker/App.xaml.cs b/src/JustABlocker/App.xaml.cs new file mode 100644 index 0000000..121b287 --- /dev/null +++ b/src/JustABlocker/App.xaml.cs @@ -0,0 +1,176 @@ +using System.Diagnostics; +using System.Drawing; +using System.Windows; +using System.Windows.Forms; +using JustABlocker.Services; + +namespace JustABlocker; + +public partial class App : Application +{ + private Mutex? _mutex; + private WfpFilterService? _wfpService; + private BlacklistManager? _blacklistManager; + private NotifyIcon? _trayIcon; + private MainWindow? _mainWindow; + + protected override void OnStartup(StartupEventArgs e) + { + _mutex = new Mutex(true, "JustABlocker-Mutex", out var createdNew); + if (!createdNew) + { + Current.Shutdown(); + return; + } + + _blacklistManager = new BlacklistManager(); + _blacklistManager.Load(); + + try + { + _wfpService = new WfpFilterService(); + _wfpService.Initialize(); + } + catch (Exception ex) + { + MessageBox.Show( + $"Failed to initialize WFP: {ex.Message}\n\nRun as Administrator.", + "Just a Blocker", + MessageBoxButton.OK, + MessageBoxImage.Warning); + } + + SetupTray(); + SyncFilters(); + + if (e.Args.Contains("--minimized") || e.Args.Contains("-m")) + { + _mainWindow = CreateMainWindow(); + } + else + { + ShowMainWindow(); + } + + if (_wfpService != null) + { + var appPath = AutoStartup.GetExecutablePath(); + if (!string.IsNullOrEmpty(appPath) && !AutoStartup.IsEnabled()) + { + AutoStartup.Enable(appPath); + } + } + } + + private void SetupTray() + { + _trayIcon = new NotifyIcon + { + Icon = CreateTrayIcon(), + Text = "Just a Blocker", + Visible = true, + }; + + var menu = new ContextMenuStrip(); + menu.Items.Add("Show / Hide", null, (_, _) => ToggleWindow()); + menu.Items.Add(new ToolStripSeparator()); + menu.Items.Add("Pause", null, (_, _) => TogglePause()); + menu.Items.Add(new ToolStripSeparator()); + menu.Items.Add("Exit", null, (_, _) => ExitApp()); + + _trayIcon.ContextMenuStrip = menu; + _trayIcon.DoubleClick += (_, _) => ToggleWindow(); + } + + private static Icon CreateTrayIcon() + { + using var bitmap = new Bitmap(16, 16); + using var g = Graphics.FromImage(bitmap); + g.Clear(Color.Black); + using var brush = new SolidBrush(Color.Red); + g.FillRectangle(brush, 3, 3, 10, 10); + using var whiteBrush = new SolidBrush(Color.White); + var font = new Font("Segoe UI", 8, FontStyle.Bold); + g.DrawString("!", font, whiteBrush, 4, 2); + var hicon = bitmap.GetHicon(); + return Icon.FromHandle(hicon); + } + + private void ShowMainWindow() + { + if (_mainWindow == null) + { + _mainWindow = CreateMainWindow(); + } + _mainWindow.Show(); + _mainWindow.Activate(); + } + + private MainWindow CreateMainWindow() + { + var w = new MainWindow(_blacklistManager!, _wfpService!); + w.Closing += (_, args) => + { + args.Cancel = true; + w.Hide(); + }; + return w; + } + + private void ToggleWindow() + { + if (_mainWindow == null || !_mainWindow.IsVisible) + { + ShowMainWindow(); + } + else + { + _mainWindow.Hide(); + } + } + + private void TogglePause() + { + if (_mainWindow != null && _mainWindow.IsVisible) + { + _mainWindow.TogglePause(); + } + else + { + if (_wfpService != null) + { + _wfpService.RemoveAll(); + } + } + } + + private void SyncFilters() + { + try + { + if (_blacklistManager == null || _wfpService == null) return; + var resolver = new DnsResolver(); + var enabled = _blacklistManager.GetEnabledEntries(); + var ips = resolver.ResolveAll(enabled); + _wfpService.SetBlockedIps(ips); + } + catch (Exception ex) + { + Trace.TraceWarning($"SyncFilters failed: {ex.Message}"); + } + } + + private void ExitApp() + { + _trayIcon?.Visible = false; + _trayIcon?.Dispose(); + Current.Shutdown(); + } + + protected override void OnExit(ExitEventArgs e) + { + _trayIcon?.Dispose(); + _mutex?.Dispose(); + base.OnExit(e); + } +} diff --git a/src/JustABlocker/JustABlocker.csproj b/src/JustABlocker/JustABlocker.csproj new file mode 100644 index 0000000..e2b7eb4 --- /dev/null +++ b/src/JustABlocker/JustABlocker.csproj @@ -0,0 +1,12 @@ + + + WinExe + net8.0-windows + true + true + enable + JustABlocker + JustABlocker + app.manifest + + diff --git a/src/JustABlocker/MainWindow.xaml b/src/JustABlocker/MainWindow.xaml new file mode 100644 index 0000000..f676c68 --- /dev/null +++ b/src/JustABlocker/MainWindow.xaml @@ -0,0 +1,79 @@ + + + + + + + + + + + + ⏸ Pause + + + + + + + + + + + + + + + + +