using SharpPcap; using SharpPcap.LibPcap; namespace gatuna; 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 { } } }