Files
gatuna/win/gatuna-client/TunnelLink.cs
T
mute 27e15452ee fix: filter out own outgoing frames on Windows client
Npcap in promiscuous mode loops back our own sent frames to the
capture callback. Without filtering, every DATA frame the client
sent was also processed as an incoming frame, duplicating the data
stream. This corrupted SSH sessions (Bad packet length 0x5353482D
= 'SSH-' — the version banner received twice).

Fix: compare source MAC in captured frames against our own MAC and
skip matches. Mirrors the PACKET_OUTGOING check on the Rust side.
2026-08-13 08:02:28 +00:00

105 lines
3.0 KiB
C#

using SharpPcap;
using SharpPcap.LibPcap;
namespace gatuna_client;
sealed class TunnelLink : IDisposable
{
LibPcapLiveDevice _device;
readonly byte[] _ourMac = new byte[6];
public event Action<Frame, byte[]>? FrameReceived;
public event Action<string>? Log;
public TunnelLink(LibPcapLiveDevice device)
{
_device = device;
}
public static LibPcapLiveDevice[] ListDevices()
{
return [.. LibPcapLiveDeviceList.Instance
.Where(d => !d.Loopback && d.MacAddress != null)];
}
public void Open()
{
_device.Open(new DeviceConfiguration
{
Mode = DeviceModes.Promiscuous | DeviceModes.MaxResponsiveness,
ReadTimeout = 1000,
});
_device.Filter = $"ether proto 0x{Proto.EtherType:X4}";
if (_device.MacAddress?.GetAddressBytes() is { Length: 6 } mac)
{
Buffer.BlockCopy(mac, 0, _ourMac, 0, 6);
}
_device.OnPacketArrival += OnPacketArrival;
_device.StartCapture();
Log?.Invoke($"capture started on {_device.Name}");
}
void OnPacketArrival(object? sender, PacketCapture capture)
{
var raw = capture.GetPacket();
var data = raw.Data;
if (data.Length < Proto.EthHeaderLen + 6)
return;
var et = (ushort)(data[12] << 8 | data[13]);
if (et != Proto.EtherType)
return;
// Skip our own outgoing frames (Npcap loops them back in promiscuous mode).
if (data[6] == _ourMac[0] && data[7] == _ourMac[1]
&& data[8] == _ourMac[2] && data[9] == _ourMac[3]
&& data[10] == _ourMac[4] && data[11] == _ourMac[5])
return;
var srcMac = new byte[6];
Buffer.BlockCopy(data, 6, srcMac, 0, 6);
var payload = data.AsSpan(Proto.EthHeaderLen);
if (FrameCodec.Parse(payload) is { } frame)
FrameReceived?.Invoke(frame, srcMac);
}
public void SendBroadcast(Frame frame)
{
SendRaw([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], FrameCodec.Encode(frame));
}
public void SendTo(byte[] dstMac, Frame frame)
{
SendRaw(dstMac, FrameCodec.Encode(frame));
}
void SendRaw(byte[] dstMac, byte[] payload)
{
var frame = new byte[Proto.EthHeaderLen + payload.Length];
Buffer.BlockCopy(dstMac, 0, frame, 0, 6);
Buffer.BlockCopy(_ourMac, 0, frame, 6, 6);
frame[12] = (byte)(Proto.EtherType >> 8);
frame[13] = (byte)(Proto.EtherType & 0xFF);
Buffer.BlockCopy(payload, 0, frame, 14, payload.Length);
try
{
_device.SendPacket(frame);
}
catch (Exception ex)
{
Log?.Invoke($"send failed: {ex.Message}");
}
}
public void Dispose()
{
try
{
if (_device.Started)
_device.StopCapture();
_device.OnPacketArrival -= OnPacketArrival;
_device.Close();
}
catch { }
}
}