diff --git a/src/GZCTF/Controllers/ProxyController.cs b/src/GZCTF/Controllers/ProxyController.cs index bf600754f..d94b4b036 100644 --- a/src/GZCTF/Controllers/ProxyController.cs +++ b/src/GZCTF/Controllers/ProxyController.cs @@ -1,6 +1,7 @@ using System.Net; using System.Net.Sockets; using System.Net.WebSockets; +using System.Text.Json; using GZCTF.Models.Internal; using GZCTF.Repositories.Interface; using GZCTF.Utils; @@ -25,6 +26,10 @@ public class ProxyController : ControllerBase private readonly bool _enableTrafficCapture = false; private const int BUFFER_SIZE = 1024 * 4; private const uint CONNECTION_LIMIT = 64; + private readonly JsonSerializerOptions _JsonOptions = new() + { + Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; public ProxyController(ILogger logger, IDistributedCache cache, IOptions provider, IContainerRepository containerRepository) @@ -60,7 +65,7 @@ public async Task ProxyForInstance(string id, CancellationToken t if (!await IncrementConnectionCount(id)) return BadRequest(new RequestResponse("容器连接数已达上限")); - var container = await _containerRepository.GetContainerById(id, token); + var container = await _containerRepository.GetContainerWithInstanceById(id, token); if (container is null || container.Instance is null || !container.IsProxy) return NotFound(new RequestResponse("不存在的容器")); @@ -76,6 +81,24 @@ public async Task ProxyForInstance(string id, CancellationToken t if (clientIp is null) return BadRequest(new RequestResponse("无效的访问地址")); + var enable = _enableTrafficCapture && container.Instance.Challenge.EnableTrafficCapture; + byte[]? metadata = null; + + if (enable) + { + metadata = JsonSerializer.SerializeToUtf8Bytes(new + { + Challenge = container.Instance.Challenge.Title, + container.Instance.ChallengeId, + + Team = container.Instance.Participation.Team.Name, + container.Instance.Participation.TeamId, + + container.ContainerId, + container.Instance.FlagContext?.Flag + }, _JsonOptions); + } + CapturableNetworkStream? stream; try { @@ -89,13 +112,15 @@ public async Task ProxyForInstance(string id, CancellationToken t return BadRequest(new RequestResponse("容器连接失败")); } - stream = new CapturableNetworkStream(socket, new() - { - Source = new(clientIp, clientPort), - Dest = ipEndPoint, - EnableCapture = _enableTrafficCapture && container.Instance.Challenge.EnableTrafficCapture, - FilePath = container.TrafficPath(HttpContext.Connection.Id), - }); + stream = new CapturableNetworkStream(socket, metadata, + new() + { + Source = new(clientIp, clientPort), + Dest = ipEndPoint, + EnableCapture = enable, + FilePath = container.TrafficPath(HttpContext.Connection.Id), + } + ); } catch (Exception e) { diff --git a/src/GZCTF/Repositories/ContainerRepository.cs b/src/GZCTF/Repositories/ContainerRepository.cs index 4cde3646a..abf057e93 100644 --- a/src/GZCTF/Repositories/ContainerRepository.cs +++ b/src/GZCTF/Repositories/ContainerRepository.cs @@ -19,8 +19,13 @@ public ContainerRepository(IDistributedCache cache, public override Task CountAsync(CancellationToken token = default) => _context.Containers.CountAsync(token); public Task GetContainerById(string guid, CancellationToken token = default) - => _context.Containers.Include(c => c.Instance) - .ThenInclude(i => i!.Challenge) + => _context.Containers.FirstOrDefaultAsync(i => i.Id == guid, token); + + public Task GetContainerWithInstanceById(string guid, CancellationToken token = default) + => _context.Containers.IgnoreAutoIncludes() + .Include(c => c.Instance).ThenInclude(i => i!.Challenge) + .Include(c => c.Instance).ThenInclude(i => i!.FlagContext) + .Include(c => c.Instance).ThenInclude(i => i!.Participation).ThenInclude(p => p.Team) .FirstOrDefaultAsync(i => i.Id == guid, token); public Task> GetContainers(CancellationToken token = default) diff --git a/src/GZCTF/Repositories/Interface/IContainerRepository.cs b/src/GZCTF/Repositories/Interface/IContainerRepository.cs index efc719910..c785706c5 100644 --- a/src/GZCTF/Repositories/Interface/IContainerRepository.cs +++ b/src/GZCTF/Repositories/Interface/IContainerRepository.cs @@ -19,6 +19,14 @@ public interface IContainerRepository : IRepository /// public Task GetContainerById(string guid, CancellationToken token = default); + /// + /// 根据容器数据库 ID 获取容器及实例信息 + /// + /// 容器数据库 ID + /// + /// + public Task GetContainerWithInstanceById(string guid, CancellationToken token = default); + /// /// 容器数据库 ID 对应容器是否存在 /// diff --git a/src/GZCTF/Utils/CapturableNetworkStream.cs b/src/GZCTF/Utils/CapturableNetworkStream.cs index df7f95113..d875be709 100644 --- a/src/GZCTF/Utils/CapturableNetworkStream.cs +++ b/src/GZCTF/Utils/CapturableNetworkStream.cs @@ -39,8 +39,9 @@ public sealed class CapturableNetworkStream : NetworkStream private readonly CapturableNetworkStreamOptions _options; private readonly CaptureFileWriterDevice? _device = null; private readonly PhysicalAddress _dummyPhysicalAddress = PhysicalAddress.Parse("00-11-00-11-00-11"); + private readonly IPEndPoint _host = new(IPAddress.Parse("0.0.0.0"), 65535); - public CapturableNetworkStream(Socket socket, CapturableNetworkStreamOptions options) : base(socket) + public CapturableNetworkStream(Socket socket, byte[]? metadata, CapturableNetworkStreamOptions options) : base(socket) { _options = options; @@ -55,6 +56,9 @@ public CapturableNetworkStream(Socket socket, CapturableNetworkStreamOptions opt _device = new(_options.FilePath, FileMode.Open); _device.Open(LinkLayers.Ethernet); + + if (metadata is not null) + WriteCapturedData(_host, _options.Source, metadata); } } @@ -65,19 +69,7 @@ public override async ValueTask ReadAsync(Memory buffer, Cancellation if (!_options.EnableCapture) return count; - var udp = new UdpPacket((ushort)_options.Dest.Port, (ushort)_options.Source.Port) - { - PayloadDataSegment = new ByteArraySegment(buffer[..count].ToArray()) - }; - - var packet = new EthernetPacket(_dummyPhysicalAddress, _dummyPhysicalAddress, EthernetType.IPv6) - { - PayloadPacket = new IPv6Packet(_options.Dest.Address, _options.Source.Address) { PayloadPacket = udp } - }; - - udp.UpdateUdpChecksum(); - - _device?.Write(new RawCapture(LinkLayers.Ethernet, new(), packet.Bytes)); + WriteCapturedData(_options.Dest, _options.Source, buffer); return count; } @@ -85,23 +77,32 @@ public override async ValueTask ReadAsync(Memory buffer, Cancellation public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) { if (_options.EnableCapture) - { - var udp = new UdpPacket((ushort)_options.Source.Port, (ushort)_options.Dest.Port) - { - PayloadDataSegment = new ByteArraySegment(buffer.ToArray()) - }; + WriteCapturedData(_options.Source, _options.Dest, buffer); - var packet = new EthernetPacket(_dummyPhysicalAddress, _dummyPhysicalAddress, EthernetType.IPv6) - { - PayloadPacket = new IPv6Packet(_options.Source.Address, _options.Dest.Address) { PayloadPacket = udp } - }; + await base.WriteAsync(buffer, cancellationToken); + } - udp.UpdateUdpChecksum(); + /// + /// 向文件写入一条数据记录 + /// + /// 源地址 + /// 目的地址 + /// 数据 + internal void WriteCapturedData(IPEndPoint source, IPEndPoint dest, ReadOnlyMemory buffer) + { + var udp = new UdpPacket((ushort)source.Port, (ushort)dest.Port) + { + PayloadDataSegment = new ByteArraySegment(buffer.ToArray()) + }; - _device?.Write(new RawCapture(LinkLayers.Ethernet, new(), packet.Bytes)); - } + var packet = new EthernetPacket(_dummyPhysicalAddress, _dummyPhysicalAddress, EthernetType.IPv6) + { + PayloadPacket = new IPv6Packet(source.Address, dest.Address) { PayloadPacket = udp } + }; - await base.WriteAsync(buffer, cancellationToken); + udp.UpdateUdpChecksum(); + + _device?.Write(new RawCapture(LinkLayers.Ethernet, new(), packet.Bytes)); } public override void Close() diff --git a/src/GZCTF/Utils/HubHelper.cs b/src/GZCTF/Utils/HubHelper.cs index d362b20b6..8fe3ef480 100644 --- a/src/GZCTF/Utils/HubHelper.cs +++ b/src/GZCTF/Utils/HubHelper.cs @@ -3,7 +3,7 @@ namespace GZCTF.Utils; -public class HubHelper +public static class HubHelper { /// /// 当前请求是否具有权限 diff --git a/src/GZCTF/Utils/LogHelper.cs b/src/GZCTF/Utils/LogHelper.cs index 7b0c5b7a7..dbeb60469 100644 --- a/src/GZCTF/Utils/LogHelper.cs +++ b/src/GZCTF/Utils/LogHelper.cs @@ -3,7 +3,6 @@ using GZCTF.Extensions; using NpgsqlTypes; using Serilog; -using Serilog.Core; using Serilog.Events; using Serilog.Sinks.File.Archive; using Serilog.Sinks.PostgreSQL; @@ -138,14 +137,14 @@ public static Serilog.ILogger GetLogger(IConfiguration configuration, IServicePr restrictedToMinimumLevel: LogEventLevel.Debug )) .WriteTo.Async(t => t.File( - path: $"files/logs/log_.log", + path: $"{FilePath.Logs}/log_.log", formatter: new ExpressionTemplate(LogTemplate), rollingInterval: RollingInterval.Day, fileSizeLimitBytes: 10 * 1024 * 1024, restrictedToMinimumLevel: LogEventLevel.Debug, rollOnFileSizeLimit: true, retainedFileCountLimit: 5, - hooks: new ArchiveHooks(CompressionLevel.Optimal, "files/logs/archive/{UtcDate:yyyy-MM}") + hooks: new ArchiveHooks(CompressionLevel.Optimal, $"{FilePath.Logs}/archive/{{UtcDate:yyyy-MM}}") )) .WriteTo.Async(t => t.PostgreSQL( connectionString: configuration.GetConnectionString("Database"),