add gatunad server and winforms client v1 (TCP-only)

Raw-Ethernet tunnel over ethertype 0x6969 to bypass WFP killswitches.
Server (Rust, AF_PACKET + classic BPF) relays TCP to 127.0.0.1 services.
Client (.NET 8 WinForms, SharpPcap/Npcap) discovers upstreams and exposes
local loopback listeners. Wire protocol: 6-byte header, 7 frame types,
MANIFEST with labeled upstreams. UDP types reserved, unimplemented.
This commit is contained in:
2026-08-12 18:20:51 +00:00
parent 606d50432c
commit 25f00b0deb
16 changed files with 1852 additions and 1 deletions
+141
View File
@@ -0,0 +1,141 @@
using SharpPcap.LibPcap;
namespace gatuna_client;
public partial class MainForm : Form
{
readonly SessionManager _sessions = new();
readonly ComboBox _deviceBox = new();
readonly Button _discoverBtn = new();
readonly ListView _listView = new();
readonly Label _statusLabel = new();
TunnelLink? _link;
public MainForm()
{
Text = "gatuna";
Width = 520;
Height = 380;
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
MinimizeBox = false;
StartPosition = FormStartPosition.CenterScreen;
InitializeComponents();
_sessions.Log += msg => this.Invoke(() => _statusLabel.Text = msg);
_sessions.ManifestReceived += entries => this.Invoke(() => PopulateList(entries));
foreach (var d in TunnelLink.ListDevices())
_deviceBox.Items.Add($"{d.Name} — {d.Interface?.FriendlyName ?? d.Interface?.Description}");
if (_deviceBox.Items.Count > 0)
_deviceBox.SelectedIndex = 0;
}
void InitializeComponents()
{
Controls.Add(new Label { Text = "Adapter:", Left = 12, Top = 12, AutoSize = true });
_deviceBox.Left = 70; _deviceBox.Top = 9;
_deviceBox.Width = 330; _deviceBox.DropDownStyle = ComboBoxStyle.DropDownList;
Controls.Add(_deviceBox);
_discoverBtn.Text = "Discover";
_discoverBtn.Left = 410; _discoverBtn.Top = 8; _discoverBtn.Width = 80;
_discoverBtn.Click += OnDiscover;
Controls.Add(_discoverBtn);
_listView.Left = 12; _listView.Top = 40;
_listView.Width = 478; _listView.Height = 250;
_listView.View = View.Details;
_listView.FullRowSelect = true;
_listView.CheckBoxes = true;
_listView.Columns.Add("ID", 36);
_listView.Columns.Add("Proto", 50);
_listView.Columns.Add("Port", 56);
_listView.Columns.Add("Label", 160);
_listView.Columns.Add("Mirror", 70);
_listView.ItemChecked += OnItemChecked;
Controls.Add(_listView);
_statusLabel.Left = 12; _statusLabel.Top = 304;
_statusLabel.Width = 478; _statusLabel.AutoEllipsis = true;
Controls.Add(_statusLabel);
}
void OnDiscover(object? s, EventArgs e)
{
if (_deviceBox.SelectedIndex < 0)
{
MessageBox.Show("Select a network adapter first.");
return;
}
if (_link != null)
{
_sessions.StopAll();
_sessions.DetachLink();
_link.Dispose();
}
var devices = TunnelLink.ListDevices();
var device = devices[_deviceBox.SelectedIndex];
_link = new TunnelLink(device);
_link.Log += msg => this.Invoke(() => _statusLabel.Text = msg);
_sessions.AttachLink(_link);
_link.Open();
_sessions.Discover();
_statusLabel.Text = "discovering...";
}
void PopulateList(UpstreamEntry[] entries)
{
_listView.BeginUpdate();
_listView.Items.Clear();
foreach (var up in entries)
{
var item = new ListViewItem(up.Id.ToString());
item.SubItems.Add(up.ProtoName);
item.SubItems.Add(up.Port.ToString());
item.SubItems.Add(up.Label ?? "");
item.SubItems.Add("");
item.Tag = up;
_listView.Items.Add(item);
}
_listView.EndUpdate();
}
void OnItemChecked(object? s, ItemCheckedEventArgs e)
{
if (e.Item.Tag is not UpstreamEntry up) return;
if (e.Item.Checked)
{
var port = _sessions.StartListener(up);
e.Item.SubItems[4].Text = port.ToString();
}
else
{
if (int.TryParse(e.Item.SubItems[4].Text, out var port) && port > 0)
{
_sessions.StopListener(port);
e.Item.SubItems[4].Text = "";
}
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide();
return;
}
base.OnFormClosing(e);
}
public void Shutdown()
{
_sessions.Dispose();
_link?.Dispose();
}
}