using DNS.Protocol; using DNS.Protocol.ResourceRecords; using FastGithub.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; using WindivertDotnet; namespace FastGithub.PacketIntercept.Dns { /// /// dns拦截器 /// [SupportedOSPlatform("windows")] sealed class DnsInterceptor : IDnsInterceptor { private static readonly Filter filter = Filter.True.And(f => f.Udp.DstPort == 53); private readonly FastGithubConfig fastGithubConfig; private readonly ILogger logger; private readonly TimeSpan ttl = TimeSpan.FromMinutes(5d); /// /// 刷新DNS缓存 /// [DllImport("dnsapi.dll", EntryPoint = "DnsFlushResolverCache", SetLastError = true)] private static extern void DnsFlushResolverCache(); /// /// dns拦截器 /// /// /// /// public DnsInterceptor( FastGithubConfig fastGithubConfig, ILogger logger, IOptionsMonitor options) { this.fastGithubConfig = fastGithubConfig; this.logger = logger; options.OnChange(_ => DnsFlushResolverCache()); } /// /// DNS拦截 /// /// /// /// public async Task InterceptAsync(CancellationToken cancellationToken) { using var divert = new WinDivert(filter, WinDivertLayer.Network); using var packet = new WinDivertPacket(); using var addr = new WinDivertAddress(); DnsFlushResolverCache(); cancellationToken.Register(DnsFlushResolverCache); while (cancellationToken.IsCancellationRequested == false) { await divert.RecvAsync(packet, addr, cancellationToken); try { this.ModifyDnsPacket(packet, addr); } catch (Exception ex) { this.logger.LogWarning(ex.Message); } finally { await divert.SendAsync(packet, addr, cancellationToken); } } } /// /// 修改DNS数据包 /// /// /// unsafe private void ModifyDnsPacket(WinDivertPacket packet, WinDivertAddress addr) { var result = packet.GetParseResult(); var requestPayload = result.DataSpan.ToArray(); if (TryParseRequest(requestPayload, out var request) == false || request.OperationCode != OperationCode.Query || request.Questions.Count == 0) { return; } var question = request.Questions.First(); if (question.Type != RecordType.A && question.Type != RecordType.AAAA) { return; } var domain = question.Name; if (this.fastGithubConfig.IsMatch(question.Name.ToString()) == false) { return; } // dns响应数据 var response = Response.FromRequest(request); var loopback = question.Type == RecordType.A ? IPAddress.Loopback : IPAddress.IPv6Loopback; var record = new IPAddressResourceRecord(domain, loopback, this.ttl); response.AnswerRecords.Add(record); // 修改payload var writer = packet.GetWriter(packet.Length - result.DataLength); writer.Write(response.ToArray()); packet.ReverseEndPoint(); packet.ApplyLengthToHeaders(); packet.CalcChecksums(addr); packet.CalcOutboundFlag(addr); addr.Flags |= WinDivertAddressFlag.Impostor; this.logger.LogInformation($"{domain}->{loopback}"); } /// /// 尝试解析请求 /// /// /// /// static bool TryParseRequest(byte[] payload, [MaybeNullWhen(false)] out Request request) { try { request = Request.FromArray(payload); return true; } catch (Exception) { request = null; return false; } } } }