using SharpPcap;
using SharpPcap.LibPcap;
namespace gatuna_client;
sealed class TunnelLink : IDisposable
{
LibPcapLiveDevice _device;
readonly byte[] _ourMac = new byte[6];
public event Action? FrameReceived;
public event Action? 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 { }
}
}