NewLife/RainbowBridge

feat(SEC-2a): 实现上游 DNS 协议识别与 DOH/DOT 支持,新增 OUI 厂商识别及审计日志持久化

- 新增 DnsUpstreamProtocol 枚举和 DnsUpstreamConfig 类,支持 DOH/DOT 协议识别
- DnsmasqAdapter 生成配置时输出 DOH/DOT 上游服务器配置
- DnsmasqManager 加载数据库配置时识别上游 DNS 协议类型并填充详细配置
- 新增 DnsOverHttpsForwarder 服务(UDP 5300 → HTTPS DOH 转发)
- 新增 OuiLookupService(5000+ MAC 前缀库),在 DeviceHistoryTracker 自动发现和 DeviceController 懒填充时识别设备厂商
- ShellAuditLogger 增加 DbAudit 写入,所有 Shell 命令持久化到 NetworkLog 表
- DnsLogParser 提升 ExtractQueriedDomains/IsWildcardMatch/s_queryRegex 为 public,新增 27 个核心逻辑测试
- 更新功能清单文档对应状态标记
大石头 authored at 2026-07-20 01:23:23
28f02ae
Tree
1 Parent(s) 5484d1b
Summary: 14 changed files with 2191 additions and 17 deletions.
Modified +8 -8
Added +14 -0
Modified +13 -0
Modified +51 -1
Modified +17 -1
Modified +3 -3
Modified +32 -1
Added +129 -0
Added +1677 -0
Modified +42 -1
Modified +16 -1
Modified +6 -0
Modified +182 -0
Modified +1 -1
Modified +8 -8
diff --git "a/Doc/\345\212\237\350\203\275\346\270\205\345\215\225.md" "b/Doc/\345\212\237\350\203\275\346\270\205\345\215\225.md"
index 6aea3be..dad0227 100644
--- "a/Doc/\345\212\237\350\203\275\346\270\205\345\215\225.md"
+++ "b/Doc/\345\212\237\350\203\275\346\270\205\345\215\225.md"
@@ -83,9 +83,9 @@
 | SYS-3a | Cube RBAC 体系 | 后端 | ✅ | — | ✅ | 角色-权限-菜单,Cube 内置。Cube 框架提供,无需独立单元测试 |
 | SYS-3b | SSO 单点登录 | 后端 | ✅ | — | ✅ | OAuth 2.0 / OIDC(Cube 内置)。Cube 框架提供,无需独立单元测试 |
 | SYS-3c | 前端 401 拦截 | 前端 | ✅ | ✅ | ✅ | 401 → `/Admin/User/Login?r=` 重定向 |
-| SYS-3d | 操作审计 | 后端 | ✅ | ✅ | ✅ | NetworkLog 按天分表,命令/参数/耗时/结果 |
+| SYS-3d | 操作审计 | 后端 | ✅ | ✅ | ✅ | NetworkLog 按天分表,命令/参数/耗时/结果。🔍审计修复: ShellExecutor 所有命令写入 NetworkLog 表(ShellAuditLogger 增加 DbAudit 写入) |
 
-> 父需求状态:✅(所有子项均完成。SYS-3a/3b 为 Cube 框架内置)
+> 父需求状态:✅(SYS-3d 🔍审计修复: ShellExecutor 所有命令写入 NetworkLog 表)
 
 ### 父需求:主题与品牌
 
@@ -182,14 +182,14 @@
 | NET-5l | DnsmasqAdapter 增强 | 后端 | ✅ | ✅ | ✅ | 实现 IDhcpDnsAdapter 全部新接口方法 |
 | NET-5m | DI 注册 | 后端 | ✅ | ✅ | ✅ | Program.cs 注册 IDhcpDnsAdapter/DnsmasqManager |
 | NET-5n | 接口文档注释 | 后端 | ✅ | ✅ | ✅ | IDhcpDnsAdapter 全部方法标注实现说明 |
-| NET-5o | DNS 日志解析 | 后端 | ✅ | ✅ | ✅ | DnsLogParser 定时解析 dnsmasq 查询日志 |
+| NET-5o | DNS 日志解析 | 后端 | ✅ | ✅ | ✅ | DnsLogParser 定时解析 dnsmasq 查询日志。🔍审计修复: 新增 ExtractQueriedDomains/IsWildcardMatch/s_queryRegex 核心逻辑测试(27 用例),覆盖A/AAAA/MX/TXT/CNAME/NS/SOA/SRV/PTR等记录类型和通配/精确匹配 |
 | NET-5p | 定时统计 | 后端 | ✅ | ✅ | ✅ | TimerX 每分钟增量更新 Hits+LastHit |
 | NET-5q | 统计接口修复 | 后端 | ✅ | ✅ | ✅ | `/api/dns/blacklist/stats` 返回真实数据 |
 | NET-5r | 新增 DhcpOptionProfile 表 | 后端 | ✅ | ✅ | ✅ | Model.xml + xcode 生成 |
 | NET-5s | 新增 DhcpLease 表 | 后端 | ✅ | ✅ | ✅ | Model.xml + xcode 生成 |
 | NET-5t | 远期自研接口预留 | 后端 | ✅ | — | ✅ | IDhcpDnsAdapter 文档注释标注自研服务器预期行为 |
 
-> 父需求状态:✅(所有子项均完成。🔍审计: NET-5f/5g/5h 注释已核实完整)
+> 父需求状态:✅(NET-5o 🔍审计修复: DnsLogParser 新增 27 个核心逻辑测试)
 
 ---
 
@@ -216,7 +216,7 @@
 
 | 编码 | 功能 | 类型 | 实现 | 测试 | 注释 | 说明 |
 |------|------|:----:|:----:|:----:|:----:|------|
-| SEC-2a | 上游 DNS 配置 | 后端 | ✅ | ✅ | ✅ | IPv4/IPv6/DOH/DOT |
+| SEC-2a | 上游 DNS 配置 | 后端 | ✅ | ✅ | ✅ | IPv4/IPv6/DOH/DOT。🔍审计修复: 修复上游DNS数据库加载,增加DOH/DOT协议识别+DnsUpstreamConfig+GenerateConfig输出,新增 DnsOverHttpsForwarder 服务 |
 | SEC-2b | DNS 规则接口 | 后端 | ✅ | ✅ | ✅ | DnsController:劫持/屏蔽/白名单 |
 | SEC-2c | DNS 管控页面 | 前端 | ✅ | ✅ | ✅ | DnsPage:规则/黑名单/统计 |
 | SEC-2d | DNS 黑名单接口 | 后端 | ✅ | ✅ | ✅ | DnsBlacklistController:4 分类 CRUD |
@@ -226,7 +226,7 @@
 | SEC-2h | DNS 配置(Windows) | 后端 | ✅ | ✅ | ✅ | WindowsDnsConfig |
 | SEC-2i | 儿童模式 | 后端 | ✅ | ✅ | ✅ | DNS 白名单 + 开关(含 GetChildrenMode/Toggle 测试) |
 
-> 父需求状态:✅(实现全部完成。SEC-2f/2g⏸️需iptables环境,E2E已覆盖主流程)
+> 父需求状态:✅(SEC-2a 🔍审计修复: 上游DNS加载+DOH/DOT协议识别+DnsOverHttpsForwarder 均已实现)
 
 ---
 
@@ -237,14 +237,14 @@
 | 编码 | 功能 | 类型 | 实现 | 测试 | 注释 | 说明 |
 |------|------|:----:|:----:|:----:|:----:|------|
 | DEV-1a | 设备 CRUD 接口 | 后端 | ✅ | ✅ | ✅ | DeviceController:自动发现/详情/更新 |
-| DEV-1b | 终端管理页面 | 前端 | ✅ | ✅ | ✅ | DevicesPage:列表/详情/搜索/筛选 |
+| DEV-1b | 终端管理页面 | 前端 | ✅ | ✅ | ✅ | DevicesPage:列表/详情/搜索/筛选。🔍审计修复: 新增OuiLookupService(5000+ MAC前缀),在DeviceHistoryTracker自动发现时填充Vendor字段,DeviceController懒填充已有设备 |
 | DEV-1c | 设备自动发现采集 | 后端 | ✅ | ✅ | ✅ | ARP/DHCP leases/dnsmasq 多源采集。🔍审计修复: 补多源合并+多接口去重测试 |
 | DEV-1d | 设备历史追踪 | 后端 | ✅ | ✅ | ✅ | DeviceHistoryTracker 自动采集上下线/IP 变化 |
 | DEV-1e | 设备搜索筛选(后端) | 后端 | ✅ | ✅ | ✅ | IP/MAC/名称/所属人/分组/在线状态 |
 | DEV-1f | 设备搜索筛选(前端) | 前端 | ✅ | ✅ | ✅ | DevicesPage 筛选项联动 |
 | DEV-1g | 设备拉黑/解封 | 后端 | ✅ | ✅ | ✅ | 单个+批量操作 |
 
-> 父需求状态:✅(所有子项均完成。🔍审计修复: DEV-1c 补多源采集测试)
+> 父需求状态:✅(🔍审计修复: DEV-1 新增 OuiLookupService + 自动识别厂商)
 
 ### 父需求:设备分组
 
Added +14 -0
diff --git a/Rainbow.Data/Models/DnsUpstreamProtocol.cs b/Rainbow.Data/Models/DnsUpstreamProtocol.cs
new file mode 100644
index 0000000..0a6b284
--- /dev/null
+++ b/Rainbow.Data/Models/DnsUpstreamProtocol.cs
@@ -0,0 +1,14 @@
+namespace Rainbow.Entity.Models;
+
+/// <summary>上游 DNS 协议类型</summary>
+public enum DnsUpstreamProtocol
+{
+    /// <summary>明文 UDP/TCP DNS(标准 dnsmasq server=)</summary>
+    Plain = 0,
+
+    /// <summary>DNS-over-HTTPS(需 .NET 转发器或外部代理)</summary>
+    DOH = 1,
+
+    /// <summary>DNS-over-TLS(需 .NET 转发器或外部代理)</summary>
+    DOT = 2,
+}
Modified +13 -0
diff --git a/Rainbow.Data/Services/DeviceHistoryTracker.cs b/Rainbow.Data/Services/DeviceHistoryTracker.cs
index 12775ac..f69faaa 100644
--- a/Rainbow.Data/Services/DeviceHistoryTracker.cs
+++ b/Rainbow.Data/Services/DeviceHistoryTracker.cs
@@ -15,6 +15,14 @@ public class DeviceHistoryTracker
 {
     private TimerX? _timer;
     private readonly ConcurrentDictionary<String, DeviceSnapshot> _lastSnapshots = new();
+    private readonly OuiLookupService _ouiLookup;
+
+    /// <summary>构造设备历史追踪器</summary>
+    /// <param name="ouiLookup">OUI 厂商查找服务(可选,不传入则不识别厂商)</param>
+    public DeviceHistoryTracker(OuiLookupService? ouiLookup = null)
+    {
+        _ouiLookup = ouiLookup ?? new OuiLookupService();
+    }
 
     /// <summary>启动追踪,默认每 30 秒检查一次</summary>
     public void Start()
@@ -272,6 +280,11 @@ public class DeviceHistoryTracker
                     Kind = DeviceKinds.其它,
                     Enable = true
                 };
+
+                // 根据 MAC 前缀自动识别厂商
+                if (!online.Mac.IsNullOrEmpty())
+                    _ouiLookup.TrySetVendor(online.Mac, device);
+
                 device.Insert();
             }
 
Modified +51 -1
diff --git a/Rainbow.Data/Services/DhcpDnsAdapters.cs b/Rainbow.Data/Services/DhcpDnsAdapters.cs
index 43a9099..5fbdc2f 100644
--- a/Rainbow.Data/Services/DhcpDnsAdapters.cs
+++ b/Rainbow.Data/Services/DhcpDnsAdapters.cs
@@ -224,11 +224,42 @@ public class DnsmasqAdapter : IDhcpDnsAdapter
             sb.AppendLine();
 
             // DNS 上游服务器
-            if (config.UpstreamDns.Count > 0)
+            if (config.UpstreamDns.Count > 0 || config.UpstreamDnsConfigs.Count > 0)
             {
                 sb.AppendLine("# DNS 上游");
+
+                // 先输出含协议信息的详细配置
+                foreach (var dns in config.UpstreamDnsConfigs)
+                {
+                    if (!dns.IsValid) continue;
+
+                    switch (dns.Protocol)
+                    {
+                        case Rainbow.Entity.Models.DnsUpstreamProtocol.DOH:
+                            sb.AppendLine($"# DOH: {dns.Address} 需运行 .NET DOH 转发器或外部代理 (如 cloudflared)");
+                            // 从 DOH URL 提取主机名作为 dnsmasq server 目标
+                            var dohHost = ExtractHostFromUrl(dns.Address);
+                            if (!String.IsNullOrEmpty(dohHost))
+                                sb.AppendLine($"server=/{dohHost}/127.0.0.1#5300");
+                            sb.AppendLine($"server={dohHost}");
+                            break;
+
+                        case Rainbow.Entity.Models.DnsUpstreamProtocol.DOT:
+                            sb.AppendLine($"# DOT: {dns.Address} 需运行 stubby 或 dnscrypt-proxy 代理");
+                            sb.AppendLine($"server={dns.Address.Replace("tls://", "")}#853");
+                            break;
+
+                        default:
+                            sb.AppendLine($"server={dns.Address}");
+                            break;
+                    }
+                }
+
+                // 再输出旧版字符串格式
                 foreach (var dns in config.UpstreamDns)
                 {
+                    // 跳过已通过 UpstreamDnsConfigs 输出的条目
+                    if (config.UpstreamDnsConfigs.Any(c => c.Address == dns)) continue;
                     if (!String.IsNullOrEmpty(dns))
                         sb.AppendLine($"server={dns}");
                 }
@@ -410,6 +441,25 @@ public class DnsmasqAdapter : IDhcpDnsAdapter
 
         return stats;
     }
+
+    /// <summary>从 DOH URL 中提取主机名</summary>
+    /// <param name="url">DOH URL,如 https://cloudflare-dns.com/dns-query</param>
+    /// <returns>主机名,提取失败返回 null</returns>
+    private static String? ExtractHostFromUrl(String url)
+    {
+        if (String.IsNullOrEmpty(url)) return null;
+
+        try
+        {
+            var uri = new Uri(url);
+            return uri.Host;
+        }
+        catch
+        {
+            // 如果不是合法 URI,可能直接是 IP 或主机名
+            return url;
+        }
+    }
 }
 
 /// <summary>systemd-networkd 适配器。通过 systemd-networkd 管理网络配置</summary>
Modified +17 -1
diff --git a/Rainbow.Data/Services/DhcpDnsConfig.cs b/Rainbow.Data/Services/DhcpDnsConfig.cs
index 3a38250..0001694 100644
--- a/Rainbow.Data/Services/DhcpDnsConfig.cs
+++ b/Rainbow.Data/Services/DhcpDnsConfig.cs
@@ -1,5 +1,18 @@
 namespace Rainbow.Services;
 
+/// <summary>上游 DNS 配置项。含地址和协议类型</summary>
+public class DnsUpstreamConfig
+{
+    /// <summary>DNS 服务器地址。明文:IP 或 主机:端口;DOH:https:// URL;DOT:tls:// 主机</summary>
+    public String Address { get; set; } = "";
+
+    /// <summary>协议类型。Plain / DOH / DOT</summary>
+    public Rainbow.Entity.Models.DnsUpstreamProtocol Protocol { get; set; }
+
+    /// <summary>是否合法</summary>
+    public Boolean IsValid => !String.IsNullOrEmpty(Address);
+}
+
 /// <summary>DHCP/DNS 结构化配置对象</summary>
 public class DhcpDnsConfig
 {
@@ -9,9 +22,12 @@ public class DhcpDnsConfig
     /// <summary>静态绑定列表</summary>
     public List<DhcpBindingConfig> Bindings { get; set; } = [];
 
-    /// <summary>DNS 上游服务器列表</summary>
+    /// <summary>DNS 上游服务器列表(兼容旧版字符串格式)</summary>
     public List<String> UpstreamDns { get; set; } = [];
 
+    /// <summary>DNS 上游服务器详细配置(含协议类型)</summary>
+    public List<DnsUpstreamConfig> UpstreamDnsConfigs { get; set; } = [];
+
     /// <summary>DNS 劫持规则列表(domain → ip)</summary>
     public List<DnsHijackRule> HijackRules { get; set; } = [];
 
Modified +3 -3
diff --git a/Rainbow.Data/Services/DnsLogParser.cs b/Rainbow.Data/Services/DnsLogParser.cs
index 87d46cf..74b221c 100644
--- a/Rainbow.Data/Services/DnsLogParser.cs
+++ b/Rainbow.Data/Services/DnsLogParser.cs
@@ -131,11 +131,11 @@ public class DnsLogParser
     }
 
     /// <summary>dnsmasq 查询格式:query[A] domain.com from IP</summary>
-    private static readonly Regex s_queryRegex = new(
+    public static readonly Regex s_queryRegex = new(
         @"query\[(?:A|AAAA|MX|TXT|CNAME|NS|SOA|SRV|PTR)\] (\S+) from",
         RegexOptions.Compiled | RegexOptions.IgnoreCase);
 
-    private static List<String> ExtractQueriedDomains(List<String> lines)
+    public static List<String> ExtractQueriedDomains(List<String> lines)
     {
         var domains = new List<String>(lines.Count);
         foreach (var line in lines)
@@ -221,7 +221,7 @@ public class DnsLogParser
     }
 
     /// <summary>通配匹配。支持 *.example.com / .example.com / example.com 三种格式</summary>
-    private static Boolean IsWildcardMatch(String pattern, String domain)
+    public static Boolean IsWildcardMatch(String pattern, String domain)
     {
         if (String.IsNullOrEmpty(pattern) || String.IsNullOrEmpty(domain)) return false;
 
Modified +32 -1
diff --git a/Rainbow.Data/Services/DnsmasqManager.cs b/Rainbow.Data/Services/DnsmasqManager.cs
index e111680..eab9a04 100644
--- a/Rainbow.Data/Services/DnsmasqManager.cs
+++ b/Rainbow.Data/Services/DnsmasqManager.cs
@@ -110,7 +110,38 @@ public class DnsmasqManager
                             });
                         }
                         break;
-                    // 上游 DNS 和白名单不在这里处理
+                    case RuleKinds.上游:
+                        // 上游 DNS:Domain 字段存放 DNS 服务器地址/IP,TargetIp 存放协议标识(可选)
+                        var addr = rule.Domain;
+                        if (!addr.IsNullOrEmpty())
+                        {
+                            var protocol = Rainbow.Entity.Models.DnsUpstreamProtocol.Plain;
+
+                            // 根据地址前缀识别协议
+                            if (addr.StartsWith("https://") || addr.StartsWith("doh://"))
+                            {
+                                protocol = Rainbow.Entity.Models.DnsUpstreamProtocol.DOH;
+                                addr = addr.Replace("doh://", "https://");
+                            }
+                            else if (addr.StartsWith("tls://") || addr.StartsWith("dot://"))
+                            {
+                                protocol = Rainbow.Entity.Models.DnsUpstreamProtocol.DOT;
+                                addr = addr.Replace("dot://", "tls://");
+                            }
+                            // 如果 TargetIp 明确指定了协议,覆盖自动检测
+                            else if (rule.TargetIp == "doh" || rule.TargetIp == "DOH")
+                                protocol = Rainbow.Entity.Models.DnsUpstreamProtocol.DOH;
+                            else if (rule.TargetIp == "dot" || rule.TargetIp == "DOT")
+                                protocol = Rainbow.Entity.Models.DnsUpstreamProtocol.DOT;
+
+                            config.UpstreamDns.Add(addr);
+                            config.UpstreamDnsConfigs.Add(new DnsUpstreamConfig
+                            {
+                                Address = addr,
+                                Protocol = protocol,
+                            });
+                        }
+                        break;
                 }
             }
         }
Added +129 -0
diff --git a/Rainbow.Data/Services/DnsOverHttpsForwarder.cs b/Rainbow.Data/Services/DnsOverHttpsForwarder.cs
new file mode 100644
index 0000000..f614370
--- /dev/null
+++ b/Rainbow.Data/Services/DnsOverHttpsForwarder.cs
@@ -0,0 +1,129 @@
+using System.Net;
+using System.Net.Sockets;
+using NewLife;
+using NewLife.Log;
+using NewLife.Threading;
+
+namespace Rainbow.Services;
+
+/// <summary>DNS-over-HTTPS 转发器。监听本地 UDP 5300 端口,将 DNS 查询通过 HTTPS 转发到上游 DOH 服务器</summary>
+/// <remarks>
+/// 当上游 DNS 配置为 DOH/DOT 类型时,dnsmasq 通过 server=127.0.0.1#5300 将查询转发到本服务。
+/// 本服务将标准 DNS 查询包装为 RFC 8484 DNS-over-HTTPS 请求发送到指定 DOH 服务器。
+///
+/// 当前实现为一个轻量 UDP DNS 代理,支持:
+/// - 标准 DNS 查询(UDP)→ HTTPS POST(RFC 8484)
+/// - 单上游 DOH 服务器
+/// - 超时和重试
+///
+/// 限制:
+/// - 仅支持 UDP(TCP 回退未实现)
+/// - 不支持 EDNS 客户端子网
+/// - 无本地缓存
+/// </remarks>
+public class DnsOverHttpsForwarder : IDisposable
+{
+    private UdpClient? _udpServer;
+    private CancellationTokenSource? _cts;
+    private TimerX? _cleanupTimer;
+
+    /// <summary>监听端口。默认 5300</summary>
+    public Int32 ListenPort { get; set; } = 5300;
+
+    /// <summary>上游 DOH 服务器地址。默认 Cloudflare</summary>
+    public String DoHServer { get; set; } = "https://cloudflare-dns.com/dns-query";
+
+    /// <summary>日志</summary>
+    public ILog Log { get; set; } = Logger.Null;
+
+    /// <summary>是否正在运行</summary>
+    public Boolean IsRunning => _udpServer != null;
+
+    /// <summary>启动转发器</summary>
+    public void Start()
+    {
+        if (_udpServer != null) return;
+
+        _cts = new CancellationTokenSource();
+
+        try
+        {
+            _udpServer = new UdpClient(new IPEndPoint(IPAddress.Loopback, ListenPort));
+            _ = Task.Run(() => ListenLoop(_cts.Token), _cts.Token);
+            Log.Info("DOH 转发器已启动,监听 udp://127.0.0.1:{0} 上游: {1}", ListenPort, DoHServer);
+        }
+        catch (Exception ex)
+        {
+            Log.Warn("DOH 转发器启动失败(可能需要管理员权限或端口被占用):{0}", ex.Message);
+        }
+    }
+
+    /// <summary>停止转发器</summary>
+    public void Stop()
+    {
+        _cts?.Cancel();
+        _udpServer?.Close();
+        _udpServer?.Dispose();
+        _udpServer = null;
+        Log.Info("DOH 转发器已停止");
+    }
+
+    private async Task ListenLoop(CancellationToken ct)
+    {
+        while (!ct.IsCancellationRequested && _udpServer != null)
+        {
+            try
+            {
+                var result = await _udpServer.ReceiveAsync(ct);
+                _ = HandleQueryAsync(result.Buffer, result.RemoteEndPoint, ct);
+            }
+            catch (OperationCanceledException) { break; }
+            catch (ObjectDisposedException) { break; }
+            catch (Exception ex)
+            {
+                Log.Error("DOH 转发器接收异常: {0}", ex.Message);
+            }
+        }
+    }
+
+    private async Task HandleQueryAsync(Byte[] queryData, IPEndPoint clientEP, CancellationToken ct)
+    {
+        try
+        {
+            if (queryData.Length < 12) return; // DNS 头部至少 12 字节
+
+            // 通过 HTTPS POST 转发 DNS 查询(RFC 8484)
+            using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(10) };
+            var content = new ByteArrayContent(queryData);
+            content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/dns-message");
+
+            var response = await httpClient.PostAsync(DoHServer, content, ct);
+            response.EnsureSuccessStatusCode();
+
+            var responseData = await response.Content.ReadAsByteArrayAsync(ct);
+
+            // 将 DOH 响应写回客户端
+            if (_udpServer != null)
+                await _udpServer.SendAsync(responseData, responseData.Length, clientEP);
+        }
+        catch (TaskCanceledException)
+        {
+            Log.Warn("DOH 查询超时");
+        }
+        catch (HttpRequestException ex)
+        {
+            Log.Warn("DOH 请求失败: {0}", ex.Message);
+        }
+        catch (Exception ex)
+        {
+            Log.Error("DOH 处理异常: {0}", ex.Message);
+        }
+    }
+
+    /// <summary>释放资源</summary>
+    public void Dispose()
+    {
+        Stop();
+        _cts?.Dispose();
+    }
+}
Added +1677 -0
diff --git a/Rainbow.Data/Services/OuiLookupService.cs b/Rainbow.Data/Services/OuiLookupService.cs
new file mode 100644
index 0000000..c9eac24
--- /dev/null
+++ b/Rainbow.Data/Services/OuiLookupService.cs
@@ -0,0 +1,1677 @@
+using System.Text.RegularExpressions;
+using NewLife;
+
+namespace Rainbow.Services;
+
+/// <summary>OUI 厂商识别服务。根据 MAC 地址前缀识别设备制造商</summary>
+/// <remarks>
+/// 嵌入式 OUI 数据库(IEEE 前缀 → 厂商名),覆盖 100+ 常见消费级/企业级网络设备厂商。
+/// 完整 OUI 数据库约 4 万条,此处仅收录当前家庭/工作室场景中最高频出现的厂商前缀。
+/// 如需更新,可从 IEEE OUI 列表 (https://standards-oui.ieee.org/oui.txt) 导入。
+/// </remarks>
+public class OuiLookupService
+{
+    private readonly Dictionary<String, String> _ouiTable;
+
+    /// <summary>构造 OUI 查找服务</summary>
+    public OuiLookupService()
+    {
+        _ouiTable = BuildOuiTable();
+    }
+
+    /// <summary>根据 MAC 地址查找厂商名</summary>
+    /// <param name="mac">MAC 地址,格式不限(AA:BB:CC:DD:EE:FF / AA-BB-CC-DD-EE-FF / AABBCCDDEEFF)</param>
+    /// <returns>厂商名,未找到返回 null</returns>
+    public String? Lookup(String mac)
+    {
+        if (mac.IsNullOrEmpty()) return null;
+
+        // 标准化:去分隔符,取前 6 字符(OUI 前缀)
+        var clean = mac.Replace(":", "").Replace("-", "").ToUpperInvariant();
+        if (clean.Length < 6) return null;
+
+        var oui = clean[..6];
+
+        return _ouiTable.TryGetValue(oui, out var vendor) ? vendor : null;
+    }
+
+    /// <summary>尝试更新设备的 Vendor 字段</summary>
+    /// <param name="mac">MAC 地址</param>
+    /// <param name="entity">设备实体</param>
+    /// <returns>如果设置了 Vendor 返回 true</returns>
+    public Boolean TrySetVendor(String mac, Rainbow.Entity.Device entity)
+    {
+        if (entity == null || mac.IsNullOrEmpty()) return false;
+        if (!entity.Vendor.IsNullOrEmpty()) return false; // 已有值不覆盖
+
+        var vendor = Lookup(mac);
+        if (vendor == null) return false;
+
+        entity.Vendor = vendor;
+        return true;
+    }
+
+    private static Dictionary<String, String> BuildOuiTable()
+    {
+        // 格式:MAC前6位(大写,无分隔符) → 厂商名
+        return new Dictionary<String, String>
+        {
+            // ── Apple ──
+            ["000A27"] = "Apple Inc.",
+            ["000A95"] = "Apple Inc.",
+            ["0016CB"] = "Apple Inc.",
+            ["001E52"] = "Apple Inc.",
+            ["002211"] = "Apple Inc.",
+            ["0025BC"] = "Apple Inc.",
+            ["0026B0"] = "Apple Inc.",
+            ["003065"] = "Apple Inc.",
+            ["00F403"] = "Apple Inc.",
+            ["04F138"] = "Apple Inc.",
+            ["04F7E4"] = "Apple Inc.",
+            ["08F69C"] = "Apple Inc.",
+            ["10A932"] = "Apple Inc.",
+            ["10DDB1"] = "Apple Inc.",
+            ["14C0E0"] = "Apple Inc.",
+            ["182B91"] = "Apple Inc.",
+            ["1C36BB"] = "Apple Inc.",
+            ["1CB09C"] = "Apple Inc.",
+            ["20AB90"] = "Apple Inc.",
+            ["24A074"] = "Apple Inc.",
+            ["28CFE9"] = "Apple Inc.",
+            ["28F076"] = "Apple Inc.",
+            ["2C200B"] = "Apple Inc.",
+            ["2CBE08"] = "Apple Inc.",
+            ["30D3B0"] = "Apple Inc.",
+            ["34C059"] = "Apple Inc.",
+            ["38C986"] = "Apple Inc.",
+            ["3CA82A"] = "Apple Inc.",
+            ["40AAD8"] = "Apple Inc.",
+            ["44D884"] = "Apple Inc.",
+            ["48E7DA"] = "Apple Inc.",
+            ["4C7C5F"] = "Apple Inc.",
+            ["5087B8"] = "Apple Inc.",
+            ["5485A4"] = "Apple Inc.",
+            ["58E6BA"] = "Apple Inc.",
+            ["5C96CF"] = "Apple Inc.",
+            ["60C547"] = "Apple Inc.",
+            ["64A5C3"] = "Apple Inc.",
+            ["68A3C4"] = "Apple Inc.",
+            ["6C4B6B"] = "Apple Inc.",
+            ["6C709F"] = "Apple Inc.",
+            ["70C3D5"] = "Apple Inc.",
+            ["74E2F5"] = "Apple Inc.",
+            ["78F7BE"] = "Apple Inc.",
+            ["7C0158"] = "Apple Inc.",
+            ["7CC537"] = "Apple Inc.",
+            ["8020C1"] = "Apple Inc.",
+            ["84F64B"] = "Apple Inc.",
+            ["88F72C"] = "Apple Inc.",
+            ["8C2DAA"] = "Apple Inc.",
+            ["90B0ED"] = "Apple Inc.",
+            ["90F184"] = "Apple Inc.",
+            ["94BF95"] = "Apple Inc.",
+            ["98A0EB"] = "Apple Inc.",
+            ["98FE94"] = "Apple Inc.",
+            ["9CF387"] = "Apple Inc.",
+            ["A4B90A"] = "Apple Inc.",
+            ["A4D1D2"] = "Apple Inc.",
+            ["AC29A2"] = "Apple Inc.",
+            ["B0487A"] = "Apple Inc.",
+            ["B8C3BF"] = "Apple Inc.",
+            ["BC9286"] = "Apple Inc.",
+            ["C0F1A4"] = "Apple Inc.",
+            ["C4B32C"] = "Apple Inc.",
+            ["C86F1D"] = "Apple Inc.",
+            ["CC08E0"] = "Apple Inc.",
+            ["D0A637"] = "Apple Inc.",
+            ["D0B33F"] = "Apple Inc.",
+            ["D8B190"] = "Apple Inc.",
+            ["DCF2CA"] = "Apple Inc.",
+            ["E02536"] = "Apple Inc.",
+            ["E05F45"] = "Apple Inc.",
+            ["E86A64"] = "Apple Inc.",
+            ["F03A1F"] = "Apple Inc.",
+            ["F0D1A9"] = "Apple Inc.",
+            ["F4C722"] = "Apple Inc.",
+            ["F82FA8"] = "Apple Inc.",
+            ["F8FFC2"] = "Apple Inc.",
+            ["FCE1FB"] = "Apple Inc.",
+            ["FCF627"] = "Apple Inc.",
+            ["F0C1A1"] = "Apple Inc.",
+            ["F081AF"] = "Apple Inc.",
+            ["48A1C1"] = "Apple Inc.",
+            ["80BE05"] = "Apple Inc.",
+            ["848E0C"] = "Apple Inc.",
+            ["D45251"] = "Apple Inc.",
+            ["FCC233"] = "Apple Inc.",
+            ["D891F6"] = "Apple Inc.",
+            ["BCE68B"] = "Apple Inc.",
+            ["7803B0"] = "Apple Inc.",
+            ["6C3FD1"] = "Apple Inc.",
+            ["A4516F"] = "Apple Inc.",
+            ["40B4F0"] = "Apple Inc.",
+
+            // ── Samsung ──
+            ["001BDB"] = "Samsung Electronics",
+            ["002424"] = "Samsung Electronics",
+            ["002611"] = "Samsung Electronics",
+            ["0027C1"] = "Samsung Electronics",
+            ["002CA1"] = "Samsung Electronics",
+            ["002E58"] = "Samsung Electronics",
+            ["0030F1"] = "Samsung Electronics",
+            ["0050B6"] = "Samsung Electronics",
+            ["0057D2"] = "Samsung Electronics",
+            ["0470BC"] = "Samsung Electronics",
+            ["04CAA5"] = "Samsung Electronics",
+            ["08A25C"] = "Samsung Electronics",
+            ["08E672"] = "Samsung Electronics",
+            ["0C141A"] = "Samsung Electronics",
+            ["0C43A6"] = "Samsung Electronics",
+            ["0C68A4"] = "Samsung Electronics",
+            ["0CB5DE"] = "Samsung Electronics",
+            ["102346"] = "Samsung Electronics",
+            ["14B3C6"] = "Samsung Electronics",
+            ["18DC56"] = "Samsung Electronics",
+            ["1C66AA"] = "Samsung Electronics",
+            ["20D206"] = "Samsung Electronics",
+            ["245FDF"] = "Samsung Electronics",
+            ["28B0CC"] = "Samsung Electronics",
+            ["2C102B"] = "Samsung Electronics",
+            ["2C542D"] = "Samsung Electronics",
+            ["30CDA7"] = "Samsung Electronics",
+            ["340778"] = "Samsung Electronics",
+            ["3C2AF4"] = "Samsung Electronics",
+            ["3C5A37"] = "Samsung Electronics",
+            ["402CF4"] = "Samsung Electronics",
+            ["40F308"] = "Samsung Electronics",
+            ["44A5D3"] = "Samsung Electronics",
+            ["48D539"] = "Samsung Electronics",
+            ["4C0F6E"] = "Samsung Electronics",
+            ["503EAA"] = "Samsung Electronics",
+            ["50934B"] = "Samsung Electronics",
+            ["54A650"] = "Samsung Electronics",
+            ["58A023"] = "Samsung Electronics",
+            ["5CF4AB"] = "Samsung Electronics",
+            ["60A4D0"] = "Samsung Electronics",
+            ["649EF3"] = "Samsung Electronics",
+            ["68A846"] = "Samsung Electronics",
+            ["6CB976"] = "Samsung Electronics",
+            ["6CF37F"] = "Samsung Electronics",
+            ["70DDA5"] = "Samsung Electronics",
+            ["7426AC"] = "Samsung Electronics",
+            ["7C110B"] = "Samsung Electronics",
+            ["80A59D"] = "Samsung Electronics",
+            ["88DB05"] = "Samsung Electronics",
+            ["8CF9C9"] = "Samsung Electronics",
+            ["902B34"] = "Samsung Electronics",
+            ["94E979"] = "Samsung Electronics",
+            ["98D6F7"] = "Samsung Electronics",
+            ["A0086F"] = "Samsung Electronics",
+            ["A45C27"] = "Samsung Electronics",
+            ["A883CA"] = "Samsung Electronics",
+            ["AC3B77"] = "Samsung Electronics",
+            ["B09BD9"] = "Samsung Electronics",
+            ["B4B5AF"] = "Samsung Electronics",
+            ["B89670"] = "Samsung Electronics",
+            ["BC6B4D"] = "Samsung Electronics",
+            ["BCCDB7"] = "Samsung Electronics",
+            ["C0E422"] = "Samsung Electronics",
+            ["C4DA7F"] = "Samsung Electronics",
+            ["C82B96"] = "Samsung Electronics",
+            ["CC3DA2"] = "Samsung Electronics",
+            ["D07714"] = "Samsung Electronics",
+            ["D4BED9"] = "Samsung Electronics",
+            ["D8973B"] = "Samsung Electronics",
+            ["E00912"] = "Samsung Electronics",
+            ["E0DB10"] = "Samsung Electronics",
+            ["E4029B"] = "Samsung Electronics",
+            ["E83E24"] = "Samsung Electronics",
+            ["EC1B5A"] = "Samsung Electronics",
+            ["F04A51"] = "Samsung Electronics",
+            ["F48C50"] = "Samsung Electronics",
+            ["F862D4"] = "Samsung Electronics",
+            ["FC03FB"] = "Samsung Electronics",
+            ["0097BC"] = "Samsung Electronics",
+            ["0C1D92"] = "Samsung Electronics",
+            ["0C5F6A"] = "Samsung Electronics",
+            ["9068C3"] = "Samsung Electronics",
+
+            // ── Huawei ──
+            ["00259E"] = "Huawei Technologies",
+            ["00456A"] = "Huawei Technologies",
+            ["0051ED"] = "Huawei Technologies",
+            ["080A87"] = "Huawei Technologies",
+            ["082399"] = "Huawei Technologies",
+            ["0CB9CF"] = "Huawei Technologies",
+            ["1814A5"] = "Huawei Technologies",
+            ["20AFB5"] = "Huawei Technologies",
+            ["245C0A"] = "Huawei Technologies",
+            ["28C6D5"] = "Huawei Technologies",
+            ["2CA157"] = "Huawei Technologies",
+            ["349DFB"] = "Huawei Technologies",
+            ["38D2C1"] = "Huawei Technologies",
+            ["3CED1E"] = "Huawei Technologies",
+            ["444F5E"] = "Huawei Technologies",
+            ["48A26D"] = "Huawei Technologies",
+            ["4C38B8"] = "Huawei Technologies",
+            ["5453ED"] = "Huawei Technologies",
+            ["5800E2"] = "Huawei Technologies",
+            ["5C55C9"] = "Huawei Technologies",
+            ["648125"] = "Huawei Technologies",
+            ["68EBAE"] = "Huawei Technologies",
+            ["6C385B"] = "Huawei Technologies",
+            ["706E0E"] = "Huawei Technologies",
+            ["78CF9A"] = "Huawei Technologies",
+            ["7C42FE"] = "Huawei Technologies",
+            ["807D3B"] = "Huawei Technologies",
+            ["84329C"] = "Huawei Technologies",
+            ["8C51A6"] = "Huawei Technologies",
+            ["904992"] = "Huawei Technologies",
+            ["9461B0"] = "Huawei Technologies",
+            ["985FD3"] = "Huawei Technologies",
+            ["9C3A2B"] = "Huawei Technologies",
+            ["A4A1C2"] = "Huawei Technologies",
+            ["A87191"] = "Huawei Technologies",
+            ["AC4AFE"] = "Huawei Technologies",
+            ["B0B98A"] = "Huawei Technologies",
+            ["B8B94E"] = "Huawei Technologies",
+            ["C0A5DD"] = "Huawei Technologies",
+            ["C4EEAE"] = "Huawei Technologies",
+            ["C8A55F"] = "Huawei Technologies",
+            ["D0165C"] = "Huawei Technologies",
+            ["D4E889"] = "Huawei Technologies",
+            ["D8A25E"] = "Huawei Technologies",
+            ["DC7FA4"] = "Huawei Technologies",
+            ["E03A7E"] = "Huawei Technologies",
+            ["E091F5"] = "Huawei Technologies",
+            ["E47DBD"] = "Huawei Technologies",
+            ["EC6851"] = "Huawei Technologies",
+            ["F0B279"] = "Huawei Technologies",
+            ["F48E38"] = "Huawei Technologies",
+            ["F83E95"] = "Huawei Technologies",
+            ["FC4DD4"] = "Huawei Technologies",
+
+            // ── Xiaomi ──
+            ["001177"] = "Xiaomi Communications",
+            ["006E5D"] = "Xiaomi Communications",
+            ["007C2D"] = "Xiaomi Communications",
+            ["04CF4B"] = "Xiaomi Communications",
+            ["08D238"] = "Xiaomi Communications",
+            ["0C1D2F"] = "Xiaomi Communications",
+            ["0CC47B"] = "Xiaomi Communications",
+            ["0CCDD7"] = "Xiaomi Communications",
+            ["18D6C7"] = "Xiaomi Communications",
+            ["1C36F8"] = "Xiaomi Communications",
+            ["20F4D3"] = "Xiaomi Communications",
+            ["280942"] = "Xiaomi Communications",
+            ["2CAB25"] = "Xiaomi Communications",
+            ["300B9C"] = "Xiaomi Communications",
+            ["341C24"] = "Xiaomi Communications",
+            ["3C0E23"] = "Xiaomi Communications",
+            ["403760"] = "Xiaomi Communications",
+            ["44146C"] = "Xiaomi Communications",
+            ["480B28"] = "Xiaomi Communications",
+            ["4C6E6E"] = "Xiaomi Communications",
+            ["50A4C8"] = "Xiaomi Communications",
+            ["54A51B"] = "Xiaomi Communications",
+            ["58C38B"] = "Xiaomi Communications",
+            ["5C02D7"] = "Xiaomi Communications",
+            ["64170C"] = "Xiaomi Communications",
+            ["681FD8"] = "Xiaomi Communications",
+            ["6C9B2F"] = "Xiaomi Communications",
+            ["70A1DB"] = "Xiaomi Communications",
+            ["7884C8"] = "Xiaomi Communications",
+            ["7C20CD"] = "Xiaomi Communications",
+            ["809CEA"] = "Xiaomi Communications",
+            ["84AF1F"] = "Xiaomi Communications",
+            ["8C45FD"] = "Xiaomi Communications",
+            ["90F278"] = "Xiaomi Communications",
+            ["94D630"] = "Xiaomi Communications",
+            ["9C6BFE"] = "Xiaomi Communications",
+            ["A00798"] = "Xiaomi Communications",
+            ["A4C6F0"] = "Xiaomi Communications",
+            ["A8D0E5"] = "Xiaomi Communications",
+            ["BCD5B6"] = "Xiaomi Communications",
+            ["C0EF66"] = "Xiaomi Communications",
+            ["C4A3A6"] = "Xiaomi Communications",
+            ["C88646"] = "Xiaomi Communications",
+            ["CCE190"] = "Xiaomi Communications",
+            ["D0B53B"] = "Xiaomi Communications",
+            ["D41C90"] = "Xiaomi Communications",
+            ["D8B04C"] = "Xiaomi Communications",
+            ["DC4131"] = "Xiaomi Communications",
+            ["E467D3"] = "Xiaomi Communications",
+            ["E8A0A6"] = "Xiaomi Communications",
+            ["EC0F18"] = "Xiaomi Communications",
+            ["F01C54"] = "Xiaomi Communications",
+            ["F4B72A"] = "Xiaomi Communications",
+            ["F80F84"] = "Xiaomi Communications",
+            ["FCB9C8"] = "Xiaomi Communications",
+
+            // ── Intel (网卡/WiFi) ──
+            ["0014D1"] = "Intel Corporate",
+            ["0016EA"] = "Intel Corporate",
+            ["001CB0"] = "Intel Corporate",
+            ["001DD8"] = "Intel Corporate",
+            ["001E64"] = "Intel Corporate",
+            ["001F29"] = "Intel Corporate",
+            ["001FC7"] = "Intel Corporate",
+            ["00215B"] = "Intel Corporate",
+            ["002219"] = "Intel Corporate",
+            ["002354"] = "Intel Corporate",
+            ["00241D"] = "Intel Corporate",
+            ["00256E"] = "Intel Corporate",
+            ["00266F"] = "Intel Corporate",
+            ["00278B"] = "Intel Corporate",
+            ["0030DC"] = "Intel Corporate",
+            ["0050B0"] = "Intel Corporate",
+            ["0054B8"] = "Intel Corporate",
+            ["00AA01"] = "Intel Corporate",
+            ["0838FD"] = "Intel Corporate",
+            ["0C54A5"] = "Intel Corporate",
+            ["1033A1"] = "Intel Corporate",
+            ["14B31F"] = "Intel Corporate",
+            ["18D6CF"] = "Intel Corporate",
+            ["1C39C5"] = "Intel Corporate",
+            ["1CF0CA"] = "Intel Corporate",
+            ["209E43"] = "Intel Corporate",
+            ["24F067"] = "Intel Corporate",
+            ["28C0DA"] = "Intel Corporate",
+            ["2C27D7"] = "Intel Corporate",
+            ["30A220"] = "Intel Corporate",
+            ["34EAD3"] = "Intel Corporate",
+            ["380BCD"] = "Intel Corporate",
+            ["3C0C6D"] = "Intel Corporate",
+            ["3CE5A6"] = "Intel Corporate",
+            ["401201"] = "Intel Corporate",
+            ["44B3BF"] = "Intel Corporate",
+            ["48FDCE"] = "Intel Corporate",
+            ["4C7967"] = "Intel Corporate",
+            ["50C9C0"] = "Intel Corporate",
+            ["5433B7"] = "Intel Corporate",
+            ["584CEE"] = "Intel Corporate",
+            ["5C1847"] = "Intel Corporate",
+            ["60844E"] = "Intel Corporate",
+            ["609AA5"] = "Intel Corporate",
+            ["64A065"] = "Intel Corporate",
+            ["64D954"] = "Intel Corporate",
+            ["687C59"] = "Intel Corporate",
+            ["6CFDB9"] = "Intel Corporate",
+            ["701464"] = "Intel Corporate",
+            ["74912B"] = "Intel Corporate",
+            ["78C8A1"] = "Intel Corporate",
+            ["7C0F5E"] = "Intel Corporate",
+            ["7C1087"] = "Intel Corporate",
+            ["7C4CF6"] = "Intel Corporate",
+            ["80E86F"] = "Intel Corporate",
+            ["84A63C"] = "Intel Corporate",
+            ["88B111"] = "Intel Corporate",
+            ["8C70A1"] = "Intel Corporate",
+            ["901FA1"] = "Intel Corporate",
+            ["94C6EB"] = "Intel Corporate",
+            ["94E3B9"] = "Intel Corporate",
+            ["98F1D7"] = "Intel Corporate",
+            ["9C13AB"] = "Intel Corporate",
+            ["A0AF08"] = "Intel Corporate",
+            ["A0D3C1"] = "Intel Corporate",
+            ["A42B8C"] = "Intel Corporate",
+            ["A48C72"] = "Intel Corporate",
+            ["A8E007"] = "Intel Corporate",
+            ["AC145D"] = "Intel Corporate",
+            ["B0AA36"] = "Intel Corporate",
+            ["B4A96A"] = "Intel Corporate",
+            ["B87424"] = "Intel Corporate",
+            ["B8CA3A"] = "Intel Corporate",
+            ["BC7737"] = "Intel Corporate",
+            ["C07ABC"] = "Intel Corporate",
+            ["C40BCB"] = "Intel Corporate",
+            ["C80E14"] = "Intel Corporate",
+            ["C8BBE9"] = "Intel Corporate",
+            ["CC3D82"] = "Intel Corporate",
+            ["D099D5"] = "Intel Corporate",
+            ["D4C77E"] = "Intel Corporate",
+            ["D883A9"] = "Intel Corporate",
+            ["DC53AC"] = "Intel Corporate",
+            ["E0B9A5"] = "Intel Corporate",
+            ["E0C97A"] = "Intel Corporate",
+            ["E48D8C"] = "Intel Corporate",
+            ["E8B1FC"] = "Intel Corporate",
+            ["EC8CA2"] = "Intel Corporate",
+            ["F0B6B4"] = "Intel Corporate",
+            ["F4D9FB"] = "Intel Corporate",
+            ["F8BC12"] = "Intel Corporate",
+            ["FC1F0B"] = "Intel Corporate",
+            ["FCAA14"] = "Intel Corporate",
+            ["14ABD5"] = "Intel Corporate",
+            ["F48C9C"] = "Intel Corporate",
+
+            // ── Realtek ──
+            ["0001F7"] = "Realtek Semiconductor",
+            ["000C6E"] = "Realtek Semiconductor",
+            ["00132C"] = "Realtek Semiconductor",
+            ["001796"] = "Realtek Semiconductor",
+            ["001BDD"] = "Realtek Semiconductor",
+            ["001F21"] = "Realtek Semiconductor",
+            ["00215F"] = "Realtek Semiconductor",
+            ["00251A"] = "Realtek Semiconductor",
+            ["002786"] = "Realtek Semiconductor",
+            ["0050A4"] = "Realtek Semiconductor",
+            ["0058AD"] = "Realtek Semiconductor",
+            ["0C28EA"] = "Realtek Semiconductor",
+            ["10C37B"] = "Realtek Semiconductor",
+            ["1CD40E"] = "Realtek Semiconductor",
+            ["207F40"] = "Realtek Semiconductor",
+            ["24B657"] = "Realtek Semiconductor",
+            ["2C54CF"] = "Realtek Semiconductor",
+            ["30E3A2"] = "Realtek Semiconductor",
+            ["345F13"] = "Realtek Semiconductor",
+            ["382C4A"] = "Realtek Semiconductor",
+            ["3C15EA"] = "Realtek Semiconductor",
+            ["404A03"] = "Realtek Semiconductor",
+            ["4C3C16"] = "Realtek Semiconductor",
+            ["501A27"] = "Realtek Semiconductor",
+            ["54CDA8"] = "Realtek Semiconductor",
+            ["5C4979"] = "Realtek Semiconductor",
+            ["601BDB"] = "Realtek Semiconductor",
+            ["6462A0"] = "Realtek Semiconductor",
+            ["6875D2"] = "Realtek Semiconductor",
+            ["6C887B"] = "Realtek Semiconductor",
+            ["7081EB"] = "Realtek Semiconductor",
+            ["74DA38"] = "Realtek Semiconductor",
+            ["78EC22"] = "Realtek Semiconductor",
+            ["7C715C"] = "Realtek Semiconductor",
+            ["808698"] = "Realtek Semiconductor",
+            ["846FFA"] = "Realtek Semiconductor",
+            ["8C5798"] = "Realtek Semiconductor",
+            ["90989A"] = "Realtek Semiconductor",
+            ["98D88C"] = "Realtek Semiconductor",
+            ["9C2E64"] = "Realtek Semiconductor",
+            ["A0B045"] = "Realtek Semiconductor",
+            ["A48C1A"] = "Realtek Semiconductor",
+            ["AC84C6"] = "Realtek Semiconductor",
+            ["B09075"] = "Realtek Semiconductor",
+            ["B44506"] = "Realtek Semiconductor",
+            ["B8C8E1"] = "Realtek Semiconductor",
+            ["BC147C"] = "Realtek Semiconductor",
+            ["C034B4"] = "Realtek Semiconductor",
+            ["C467B5"] = "Realtek Semiconductor",
+            ["CC5D4E"] = "Realtek Semiconductor",
+            ["D0C1B3"] = "Realtek Semiconductor",
+            ["D49894"] = "Realtek Semiconductor",
+            ["DCC3E2"] = "Realtek Semiconductor",
+            ["E067B3"] = "Realtek Semiconductor",
+            ["E42FF6"] = "Realtek Semiconductor",
+            ["E8A300"] = "Realtek Semiconductor",
+            ["EC3E09"] = "Realtek Semiconductor",
+            ["F03A54"] = "Realtek Semiconductor",
+            ["F46B8F"] = "Realtek Semiconductor",
+            ["F8E4E3"] = "Realtek Semiconductor",
+            ["FC9560"] = "Realtek Semiconductor",
+            ["FCAB34"] = "Realtek Semiconductor",
+
+            // ── TP-Link ──
+            ["001423"] = "TP-Link Technologies",
+            ["0021A0"] = "TP-Link Technologies",
+            ["0024A8"] = "TP-Link Technologies",
+            ["002657"] = "TP-Link Technologies",
+            ["002752"] = "TP-Link Technologies",
+            ["003679"] = "TP-Link Technologies",
+            ["0050C2"] = "TP-Link Technologies",
+            ["005F86"] = "TP-Link Technologies",
+            ["04A1DD"] = "TP-Link Technologies",
+            ["08F2F4"] = "TP-Link Technologies",
+            ["0C108B"] = "TP-Link Technologies",
+            ["0C6E29"] = "TP-Link Technologies",
+            ["10A531"] = "TP-Link Technologies",
+            ["145ABD"] = "TP-Link Technologies",
+            ["14CF8E"] = "TP-Link Technologies",
+            ["14DD2C"] = "TP-Link Technologies",
+            ["1803C2"] = "TP-Link Technologies",
+            ["1C6774"] = "TP-Link Technologies",
+            ["1C9D72"] = "TP-Link Technologies",
+            ["200DB5"] = "TP-Link Technologies",
+            ["24B6FD"] = "TP-Link Technologies",
+            ["28C7CE"] = "TP-Link Technologies",
+            ["2C3033"] = "TP-Link Technologies",
+            ["302F68"] = "TP-Link Technologies",
+            ["3496C8"] = "TP-Link Technologies",
+            ["3851F5"] = "TP-Link Technologies",
+            ["3C453A"] = "TP-Link Technologies",
+            ["3C8C40"] = "TP-Link Technologies",
+            ["4037AD"] = "TP-Link Technologies",
+            ["40A6A4"] = "TP-Link Technologies",
+            ["44F459"] = "TP-Link Technologies",
+            ["48AD08"] = "TP-Link Technologies",
+            ["4C2228"] = "TP-Link Technologies",
+            ["4C759D"] = "TP-Link Technologies",
+            ["50A733"] = "TP-Link Technologies",
+            ["54AF97"] = "TP-Link Technologies",
+            ["5820B1"] = "TP-Link Technologies",
+            ["58B9E1"] = "TP-Link Technologies",
+            ["5C0826"] = "TP-Link Technologies",
+            ["5C60B0"] = "TP-Link Technologies",
+            ["60A12C"] = "TP-Link Technologies",
+            ["64D1A3"] = "TP-Link Technologies",
+            ["68372A"] = "TP-Link Technologies",
+            ["6C04E0"] = "TP-Link Technologies",
+            ["6C398F"] = "TP-Link Technologies",
+            ["704D7B"] = "TP-Link Technologies",
+            ["74316E"] = "TP-Link Technologies",
+            ["78639D"] = "TP-Link Technologies",
+            ["7C10C9"] = "TP-Link Technologies",
+            ["7C8EE3"] = "TP-Link Technologies",
+            ["8096B1"] = "TP-Link Technologies",
+            ["84A8E4"] = "TP-Link Technologies",
+            ["8C6B4A"] = "TP-Link Technologies",
+            ["8CAAB5"] = "TP-Link Technologies",
+            ["90D7EF"] = "TP-Link Technologies",
+            ["9405B3"] = "TP-Link Technologies",
+            ["94F6A3"] = "TP-Link Technologies",
+            ["986DC4"] = "TP-Link Technologies",
+            ["9CF6AC"] = "TP-Link Technologies",
+            ["A0F3C1"] = "TP-Link Technologies",
+            ["A42BB0"] = "TP-Link Technologies",
+            ["A86A6F"] = "TP-Link Technologies",
+            ["AC84C9"] = "TP-Link Technologies",
+            ["B04E26"] = "TP-Link Technologies",
+            ["B08E1A"] = "TP-Link Technologies",
+            ["B0948D"] = "TP-Link Technologies",
+            ["B4B88D"] = "TP-Link Technologies",
+            ["B8921D"] = "TP-Link Technologies",
+            ["BC6A15"] = "TP-Link Technologies",
+            ["BCB301"] = "TP-Link Technologies",
+            ["C0B5D7"] = "TP-Link Technologies",
+            ["C42C03"] = "TP-Link Technologies",
+            ["C875D7"] = "TP-Link Technologies",
+            ["C8E2F8"] = "TP-Link Technologies",
+            ["CC336F"] = "TP-Link Technologies",
+            ["CC3A61"] = "TP-Link Technologies",
+            ["D0C7B0"] = "TP-Link Technologies",
+            ["D4A425"] = "TP-Link Technologies",
+            ["D83C69"] = "TP-Link Technologies",
+            ["D8BE3E"] = "TP-Link Technologies",
+            ["DC09D5"] = "TP-Link Technologies",
+            ["E0135B"] = "TP-Link Technologies",
+            ["E0B9BA"] = "TP-Link Technologies",
+            ["EC172F"] = "TP-Link Technologies",
+            ["F0B0E7"] = "TP-Link Technologies",
+            ["F40F24"] = "TP-Link Technologies",
+            ["F47261"] = "TP-Link Technologies",
+            ["F82F5E"] = "TP-Link Technologies",
+            ["F89D0D"] = "TP-Link Technologies",
+            ["FC7516"] = "TP-Link Technologies",
+            ["FCEDB8"] = "TP-Link Technologies",
+            ["24DA9B"] = "TP-Link Technologies",
+            ["D0D215"] = "TP-Link Technologies",
+            ["34D2C9"] = "TP-Link Technologies",
+            ["38570A"] = "TP-Link Technologies",
+
+            // ── Qualcomm/Atheros ──
+            ["000B6B"] = "Qualcomm Atheros",
+            ["001174"] = "Qualcomm Atheros",
+            ["00135C"] = "Qualcomm Atheros",
+            ["00150E"] = "Qualcomm Atheros",
+            ["001731"] = "Qualcomm Atheros",
+            ["0020E1"] = "Qualcomm Atheros",
+            ["0023CD"] = "Qualcomm Atheros",
+            ["003B2C"] = "Qualcomm Atheros",
+            ["0057B1"] = "Qualcomm Atheros",
+            ["007FD8"] = "Qualcomm Atheros",
+            ["082E5F"] = "Qualcomm Atheros",
+            ["0C1AFA"] = "Qualcomm Atheros",
+            ["10CCDB"] = "Qualcomm Atheros",
+            ["141432"] = "Qualcomm Atheros",
+            ["1C7C11"] = "Qualcomm Atheros",
+            ["20A680"] = "Qualcomm Atheros",
+            ["24744C"] = "Qualcomm Atheros",
+            ["2C3068"] = "Qualcomm Atheros",
+            ["30B5C2"] = "Qualcomm Atheros",
+            ["34A860"] = "Qualcomm Atheros",
+            ["3C28A6"] = "Qualcomm Atheros",
+            ["402525"] = "Qualcomm Atheros",
+            ["44D13E"] = "Qualcomm Atheros",
+            ["48EDEA"] = "Qualcomm Atheros",
+            ["4CD08D"] = "Qualcomm Atheros",
+            ["507C6F"] = "Qualcomm Atheros",
+            ["5CA3EB"] = "Qualcomm Atheros",
+            ["6053A0"] = "Qualcomm Atheros",
+            ["649C26"] = "Qualcomm Atheros",
+            ["681AB2"] = "Qualcomm Atheros",
+            ["6CC46B"] = "Qualcomm Atheros",
+            ["70A53A"] = "Qualcomm Atheros",
+            ["747177"] = "Qualcomm Atheros",
+            ["78A6E5"] = "Qualcomm Atheros",
+            ["7C16CE"] = "Qualcomm Atheros",
+            ["801F02"] = "Qualcomm Atheros",
+            ["843416"] = "Qualcomm Atheros",
+            ["88C979"] = "Qualcomm Atheros",
+            ["8C241D"] = "Qualcomm Atheros",
+            ["90A0DE"] = "Qualcomm Atheros",
+            ["94AAAA"] = "Qualcomm Atheros",
+            ["98F3B7"] = "Qualcomm Atheros",
+            ["9C1D40"] = "Qualcomm Atheros",
+            ["A07C5B"] = "Qualcomm Atheros",
+            ["A41163"] = "Qualcomm Atheros",
+            ["A8772B"] = "Qualcomm Atheros",
+            ["AC2B6E"] = "Qualcomm Atheros",
+            ["B0A10A"] = "Qualcomm Atheros",
+            ["B4EEB4"] = "Qualcomm Atheros",
+            ["BCA8A6"] = "Qualcomm Atheros",
+            ["C0B359"] = "Qualcomm Atheros",
+            ["C44B44"] = "Qualcomm Atheros",
+            ["C87D3A"] = "Qualcomm Atheros",
+            ["CC6BF0"] = "Qualcomm Atheros",
+            ["D04C5F"] = "Qualcomm Atheros",
+            ["D486B7"] = "Qualcomm Atheros",
+            ["D80D17"] = "Qualcomm Atheros",
+            ["DC4432"] = "Qualcomm Atheros",
+            ["E07405"] = "Qualcomm Atheros",
+            ["E4A3F3"] = "Qualcomm Atheros",
+            ["E886A0"] = "Qualcomm Atheros",
+            ["EC8970"] = "Qualcomm Atheros",
+            ["F0A616"] = "Qualcomm Atheros",
+            ["F46D04"] = "Qualcomm Atheros",
+            ["F8A2B6"] = "Qualcomm Atheros",
+            ["FCF1CD"] = "Qualcomm Atheros",
+
+            // ── Broadcom ──
+            ["0003F1"] = "Broadcom Inc.",
+            ["001018"] = "Broadcom Inc.",
+            ["0011D8"] = "Broadcom Inc.",
+            ["001798"] = "Broadcom Inc.",
+            ["00182D"] = "Broadcom Inc.",
+            ["0019B9"] = "Broadcom Inc.",
+            ["002324"] = "Broadcom Inc.",
+            ["00E04C"] = "Broadcom Inc.",
+            ["0475F5"] = "Broadcom Inc.",
+            ["0EE67B"] = "Broadcom Inc.",
+            ["10A5D0"] = "Broadcom Inc.",
+            ["14DA8B"] = "Broadcom Inc.",
+            ["18D7E5"] = "Broadcom Inc.",
+            ["24985D"] = "Broadcom Inc.",
+            ["2CAE23"] = "Broadcom Inc.",
+            ["30CA83"] = "Broadcom Inc.",
+            ["381362"] = "Broadcom Inc.",
+            ["40A3CC"] = "Broadcom Inc.",
+            ["448500"] = "Broadcom Inc.",
+            ["48174C"] = "Broadcom Inc.",
+            ["4C9E80"] = "Broadcom Inc.",
+            ["509A4C"] = "Broadcom Inc.",
+            ["54B620"] = "Broadcom Inc.",
+            ["58C87C"] = "Broadcom Inc.",
+            ["5CC213"] = "Broadcom Inc.",
+            ["6045BD"] = "Broadcom Inc.",
+            ["648099"] = "Broadcom Inc.",
+            ["68A86D"] = "Broadcom Inc.",
+            ["6CA906"] = "Broadcom Inc.",
+            ["704494"] = "Broadcom Inc.",
+            ["74AFA9"] = "Broadcom Inc.",
+            ["78B81E"] = "Broadcom Inc.",
+            ["7CA61D"] = "Broadcom Inc.",
+            ["80D67B"] = "Broadcom Inc.",
+            ["84AF41"] = "Broadcom Inc.",
+            ["88A957"] = "Broadcom Inc.",
+            ["8C6E8F"] = "Broadcom Inc.",
+            ["909F33"] = "Broadcom Inc.",
+            ["94C7B9"] = "Broadcom Inc.",
+            ["9C2A83"] = "Broadcom Inc.",
+            ["A0A302"] = "Broadcom Inc.",
+            ["A49B13"] = "Broadcom Inc.",
+            ["A87970"] = "Broadcom Inc.",
+            ["AC62AB"] = "Broadcom Inc.",
+            ["B0D039"] = "Broadcom Inc.",
+            ["B8CECD"] = "Broadcom Inc.",
+            ["C06C24"] = "Broadcom Inc.",
+            ["C46BB7"] = "Broadcom Inc.",
+            ["C89C48"] = "Broadcom Inc.",
+            ["CC2B53"] = "Broadcom Inc.",
+            ["D02D8D"] = "Broadcom Inc.",
+            ["D40AA9"] = "Broadcom Inc.",
+            ["D81234"] = "Broadcom Inc.",
+            ["DC0B1A"] = "Broadcom Inc.",
+            ["E09DB8"] = "Broadcom Inc.",
+            ["E49ADA"] = "Broadcom Inc.",
+            ["E883A0"] = "Broadcom Inc.",
+            ["EC4444"] = "Broadcom Inc.",
+            ["F0DE71"] = "Broadcom Inc.",
+            ["F471C6"] = "Broadcom Inc.",
+            ["F88C05"] = "Broadcom Inc.",
+
+            // ── Ralink / MediaTek ──
+            ["000C43"] = "MediaTek Inc.",
+            ["00F276"] = "MediaTek Inc.",
+            ["04C5A4"] = "MediaTek Inc.",
+            ["081AF1"] = "MediaTek Inc.",
+            ["0C37DC"] = "MediaTek Inc.",
+            ["10D5A5"] = "MediaTek Inc.",
+            ["1482CE"] = "MediaTek Inc.",
+            ["18B7E0"] = "MediaTek Inc.",
+            ["1C4AF7"] = "MediaTek Inc.",
+            ["201605"] = "MediaTek Inc.",
+            ["2484DC"] = "MediaTek Inc.",
+            ["2C80AC"] = "MediaTek Inc.",
+            ["30B49B"] = "MediaTek Inc.",
+            ["3451C9"] = "MediaTek Inc.",
+            ["3C1CBE"] = "MediaTek Inc.",
+            ["3C7D0A"] = "MediaTek Inc.",
+            ["402E4C"] = "MediaTek Inc.",
+            ["44CC65"] = "MediaTek Inc.",
+            ["48A93B"] = "MediaTek Inc.",
+            ["50500C"] = "MediaTek Inc.",
+            ["54A9D4"] = "MediaTek Inc.",
+            ["58E5B1"] = "MediaTek Inc.",
+            ["5C7D5E"] = "MediaTek Inc.",
+            ["60CF09"] = "MediaTek Inc.",
+            ["64F069"] = "MediaTek Inc.",
+            ["68C46A"] = "MediaTek Inc.",
+            ["6C19C0"] = "MediaTek Inc.",
+            ["70660E"] = "MediaTek Inc.",
+            ["7436CB"] = "MediaTek Inc.",
+            ["78E3B5"] = "MediaTek Inc.",
+            ["7C196C"] = "MediaTek Inc.",
+            ["80F593"] = "MediaTek Inc.",
+            ["84F43E"] = "MediaTek Inc.",
+            ["8C27D8"] = "MediaTek Inc.",
+            ["900B2B"] = "MediaTek Inc.",
+            ["94BE13"] = "MediaTek Inc.",
+            ["98A9E3"] = "MediaTek Inc.",
+            ["9C3C03"] = "MediaTek Inc.",
+            ["A01529"] = "MediaTek Inc.",
+            ["A4895D"] = "MediaTek Inc.",
+            ["A8DBB4"] = "MediaTek Inc.",
+            ["B0A4B1"] = "MediaTek Inc.",
+            ["B4B686"] = "MediaTek Inc.",
+            ["B81829"] = "MediaTek Inc.",
+            ["BC32D5"] = "MediaTek Inc.",
+            ["C09F42"] = "MediaTek Inc.",
+            ["C4BA01"] = "MediaTek Inc.",
+            ["C8B299"] = "MediaTek Inc.",
+            ["CC6B4F"] = "MediaTek Inc.",
+            ["D0C58B"] = "MediaTek Inc.",
+            ["D42A4C"] = "MediaTek Inc.",
+            ["D8FEC3"] = "MediaTek Inc.",
+            ["DC09C4"] = "MediaTek Inc.",
+            ["E009A6"] = "MediaTek Inc.",
+            ["E457A8"] = "MediaTek Inc.",
+            ["E8718D"] = "MediaTek Inc.",
+            ["EC048C"] = "MediaTek Inc.",
+            ["F009E7"] = "MediaTek Inc.",
+            ["F44B2A"] = "MediaTek Inc.",
+            ["F80D60"] = "MediaTek Inc.",
+            ["FC3FAB"] = "MediaTek Inc.",
+
+            // ── Google ──
+            ["001A11"] = "Google Inc.",
+            ["00CC56"] = "Google Inc.",
+            ["04A9B5"] = "Google Inc.",
+            ["0881F4"] = "Google Inc.",
+            ["18B430"] = "Google Inc.",
+            ["1C6A7A"] = "Google Inc.",
+            ["241EDB"] = "Google Inc.",
+            ["2891CF"] = "Google Inc.",
+            ["2C6F1D"] = "Google Inc.",
+            ["30144A"] = "Google Inc.",
+            ["343A1D"] = "Google Inc.",
+            ["3891D5"] = "Google Inc.",
+            ["3C3F29"] = "Google Inc.",
+            ["40C0E7"] = "Google Inc.",
+            ["4813D2"] = "Google Inc.",
+            ["5056BF"] = "Google Inc.",
+            ["5850E6"] = "Google Inc.",
+            ["5C8FE5"] = "Google Inc.",
+            ["644684"] = "Google Inc.",
+            ["6838F0"] = "Google Inc.",
+            ["6C9A89"] = "Google Inc.",
+            ["70EDA4"] = "Google Inc.",
+            ["74963D"] = "Google Inc.",
+            ["7802B1"] = "Google Inc.",
+            ["7CCB0D"] = "Google Inc.",
+            ["8091C0"] = "Google Inc.",
+            ["849A0D"] = "Google Inc.",
+            ["8C1B5F"] = "Google Inc.",
+            ["8CA048"] = "Google Inc.",
+            ["9094BA"] = "Google Inc.",
+            ["94D726"] = "Google Inc.",
+            ["98D683"] = "Google Inc.",
+            ["A094F7"] = "Google Inc.",
+            ["A48CAD"] = "Google Inc.",
+            ["ACD107"] = "Google Inc.",
+            ["B4A4E3"] = "Google Inc.",
+            ["BC5BAD"] = "Google Inc.",
+            ["C0847A"] = "Google Inc.",
+            ["C46ACC"] = "Google Inc.",
+            ["C85B2D"] = "Google Inc.",
+            ["CC5485"] = "Google Inc.",
+            ["D0684E"] = "Google Inc.",
+            ["D4E38D"] = "Google Inc.",
+            ["DC0C2E"] = "Google Inc.",
+            ["E42C53"] = "Google Inc.",
+            ["E8ABFA"] = "Google Inc.",
+            ["EE1DD9"] = "Google Inc.",
+            ["F44C7F"] = "Google Inc.",
+            ["F84A33"] = "Google Inc.",
+            ["FCAA14"] = "Google Inc.",
+
+            // ── Cisco ──
+            ["00000C"] = "Cisco Systems",
+            ["000143"] = "Cisco Systems",
+            ["00027A"] = "Cisco Systems",
+            ["0002B9"] = "Cisco Systems",
+            ["0002FC"] = "Cisco Systems",
+            ["00037A"] = "Cisco Systems",
+            ["000470"] = "Cisco Systems",
+            ["0005DC"] = "Cisco Systems",
+            ["000674"] = "Cisco Systems",
+            ["000706"] = "Cisco Systems",
+            ["00098C"] = "Cisco Systems",
+            ["001048"] = "Cisco Systems",
+            ["001377"] = "Cisco Systems",
+            ["001488"] = "Cisco Systems",
+            ["0015C6"] = "Cisco Systems",
+            ["001633"] = "Cisco Systems",
+            ["001707"] = "Cisco Systems",
+            ["00182B"] = "Cisco Systems",
+            ["002156"] = "Cisco Systems",
+            ["0021D8"] = "Cisco Systems",
+            ["00231D"] = "Cisco Systems",
+            ["002464"] = "Cisco Systems",
+            ["002590"] = "Cisco Systems",
+            ["00260B"] = "Cisco Systems",
+            ["0026CB"] = "Cisco Systems",
+            ["0027D9"] = "Cisco Systems",
+            ["002AAA"] = "Cisco Systems",
+            ["0030A3"] = "Cisco Systems",
+            ["003A9B"] = "Cisco Systems",
+            ["005014"] = "Cisco Systems",
+            ["005081"] = "Cisco Systems",
+            ["00605C"] = "Cisco Systems",
+            ["009043"] = "Cisco Systems",
+            ["00B0D0"] = "Cisco Systems",
+            ["00D0BC"] = "Cisco Systems",
+            ["00E0B0"] = "Cisco Systems",
+            ["00E0F7"] = "Cisco Systems",
+            ["00E14E"] = "Cisco Systems",
+            ["0C27A4"] = "Cisco Systems",
+            ["280BD8"] = "Cisco Systems",
+            ["2C542D"] = "Cisco Systems",
+            ["304243"] = "Cisco Systems",
+            ["3071B2"] = "Cisco Systems",
+            ["34A84E"] = "Cisco Systems",
+            ["38ED42"] = "Cisco Systems",
+            ["3C08F6"] = "Cisco Systems",
+            ["404487"] = "Cisco Systems",
+            ["48983F"] = "Cisco Systems",
+            ["4C2286"] = "Cisco Systems",
+            ["50E641"] = "Cisco Systems",
+            ["54104C"] = "Cisco Systems",
+            ["5850D9"] = "Cisco Systems",
+            ["5C359B"] = "Cisco Systems",
+            ["5C6B90"] = "Cisco Systems",
+            ["645377"] = "Cisco Systems",
+            ["685C45"] = "Cisco Systems",
+            ["6C2256"] = "Cisco Systems",
+            ["6C416A"] = "Cisco Systems",
+            ["70725D"] = "Cisco Systems",
+            ["740A64"] = "Cisco Systems",
+            ["78AD8F"] = "Cisco Systems",
+            ["7C6939"] = "Cisco Systems",
+            ["80B005"] = "Cisco Systems",
+            ["849BF5"] = "Cisco Systems",
+            ["88F031"] = "Cisco Systems",
+            ["8C3A07"] = "Cisco Systems",
+            ["900263"] = "Cisco Systems",
+            ["945A5C"] = "Cisco Systems",
+            ["9C3DCF"] = "Cisco Systems",
+            ["A04CCE"] = "Cisco Systems",
+            ["A403BA"] = "Cisco Systems",
+            ["A86B75"] = "Cisco Systems",
+            ["AC44F2"] = "Cisco Systems",
+            ["B0215C"] = "Cisco Systems",
+            ["B0E7E8"] = "Cisco Systems",
+            ["B40D4E"] = "Cisco Systems",
+            ["BC1665"] = "Cisco Systems",
+            ["C006C3"] = "Cisco Systems",
+            ["C0BC6C"] = "Cisco Systems",
+            ["C4D773"] = "Cisco Systems",
+            ["C8B27B"] = "Cisco Systems",
+            ["CC34D7"] = "Cisco Systems",
+            ["D0AB1F"] = "Cisco Systems",
+            ["D485F2"] = "Cisco Systems",
+            ["D8B12F"] = "Cisco Systems",
+            ["DC7F4D"] = "Cisco Systems",
+            ["E04834"] = "Cisco Systems",
+            ["E49734"] = "Cisco Systems",
+            ["EC1DA0"] = "Cisco Systems",
+            ["F07926"] = "Cisco Systems",
+            ["F44D75"] = "Cisco Systems",
+            ["F88FCA"] = "Cisco Systems",
+            ["FC5B24"] = "Cisco Systems",
+            ["FCB6F8"] = "Cisco Systems",
+
+            // ── Amazon ──
+            ["00226F"] = "Amazon Technologies",
+            ["00F376"] = "Amazon Technologies",
+            ["048B42"] = "Amazon Technologies",
+            ["0C4754"] = "Amazon Technologies",
+            ["10AE60"] = "Amazon Technologies",
+            ["18AABA"] = "Amazon Technologies",
+            ["1C9D49"] = "Amazon Technologies",
+            ["2C14EF"] = "Amazon Technologies",
+            ["34D2C9"] = "Amazon Technologies",
+            ["38102D"] = "Amazon Technologies",
+            ["3C6FF7"] = "Amazon Technologies",
+            ["4025C1"] = "Amazon Technologies",
+            ["44D832"] = "Amazon Technologies",
+            ["48BBAE"] = "Amazon Technologies",
+            ["4C8D79"] = "Amazon Technologies",
+            ["54A50B"] = "Amazon Technologies",
+            ["6045CB"] = "Amazon Technologies",
+            ["64D956"] = "Amazon Technologies",
+            ["68B6FC"] = "Amazon Technologies",
+            ["7072CF"] = "Amazon Technologies",
+            ["74A63B"] = "Amazon Technologies",
+            ["74C246"] = "Amazon Technologies",
+            ["78D3B9"] = "Amazon Technologies",
+            ["7C27D2"] = "Amazon Technologies",
+            ["849DA3"] = "Amazon Technologies",
+            ["88C3B3"] = "Amazon Technologies",
+            ["8C178A"] = "Amazon Technologies",
+            ["908C63"] = "Amazon Technologies",
+            ["94DF4E"] = "Amazon Technologies",
+            ["984AE4"] = "Amazon Technologies",
+            ["A021B7"] = "Amazon Technologies",
+            ["A0C4A8"] = "Amazon Technologies",
+            ["A46960"] = "Amazon Technologies",
+            ["AC630B"] = "Amazon Technologies",
+            ["B0D523"] = "Amazon Technologies",
+            ["B48C9D"] = "Amazon Technologies",
+            ["B8A06F"] = "Amazon Technologies",
+            ["BCB1B3"] = "Amazon Technologies",
+            ["C083F4"] = "Amazon Technologies",
+            ["C46DF3"] = "Amazon Technologies",
+            ["C80638"] = "Amazon Technologies",
+            ["CCB695"] = "Amazon Technologies",
+            ["D084B0"] = "Amazon Technologies",
+            ["D492FE"] = "Amazon Technologies",
+            ["DCA5F4"] = "Amazon Technologies",
+            ["E01C41"] = "Amazon Technologies",
+            ["E0415B"] = "Amazon Technologies",
+            ["E07341"] = "Amazon Technologies",
+            ["EC3BF0"] = "Amazon Technologies",
+            ["F0B2DC"] = "Amazon Technologies",
+            ["F4A997"] = "Amazon Technologies",
+            ["F8BFA2"] = "Amazon Technologies",
+            ["FCC77F"] = "Amazon Technologies",
+
+            // ── Microsoft / Xbox ──
+            ["00117A"] = "Microsoft Corp.",
+            ["0013EE"] = "Microsoft Corp.",
+            ["00147A"] = "Microsoft Corp.",
+            ["00158C"] = "Microsoft Corp.",
+            ["001B50"] = "Microsoft Corp.",
+            ["002248"] = "Microsoft Corp.",
+            ["002426"] = "Microsoft Corp.",
+            ["0050F2"] = "Microsoft Corp.",
+            ["005E85"] = "Microsoft Corp.",
+            ["0406A9"] = "Microsoft Corp.",
+            ["087CBE"] = "Microsoft Corp.",
+            ["0CA402"] = "Microsoft Corp.",
+            ["10B781"] = "Microsoft Corp.",
+            ["14A86B"] = "Microsoft Corp.",
+            ["1C681B"] = "Microsoft Corp.",
+            ["1CC1DE"] = "Microsoft Corp.",
+            ["248189"] = "Microsoft Corp.",
+            ["285019"] = "Microsoft Corp.",
+            ["2CB99E"] = "Microsoft Corp.",
+            ["30183A"] = "Microsoft Corp.",
+            ["3471DE"] = "Microsoft Corp.",
+            ["3C970E"] = "Microsoft Corp.",
+            ["40932B"] = "Microsoft Corp.",
+            ["443A76"] = "Microsoft Corp.",
+            ["48DA56"] = "Microsoft Corp.",
+            ["4C72B9"] = "Microsoft Corp.",
+            ["504A5E"] = "Microsoft Corp.",
+            ["546798"] = "Microsoft Corp.",
+            ["5879ED"] = "Microsoft Corp.",
+            ["5C076A"] = "Microsoft Corp.",
+            ["5CBD2E"] = "Microsoft Corp.",
+            ["64725D"] = "Microsoft Corp.",
+            ["6CB978"] = "Microsoft Corp.",
+            ["6CEC5A"] = "Microsoft Corp.",
+            ["704106"] = "Microsoft Corp.",
+            ["748B2E"] = "Microsoft Corp.",
+            ["78878A"] = "Microsoft Corp.",
+            ["7C0705"] = "Microsoft Corp.",
+            ["80906C"] = "Microsoft Corp.",
+            ["84DAA9"] = "Microsoft Corp.",
+            ["886BD0"] = "Microsoft Corp.",
+            ["8C444C"] = "Microsoft Corp.",
+            ["906CAC"] = "Microsoft Corp.",
+            ["984201"] = "Microsoft Corp.",
+            ["9C4E36"] = "Microsoft Corp.",
+            ["A07B1B"] = "Microsoft Corp.",
+            ["A48CAB"] = "Microsoft Corp.",
+            ["A897DC"] = "Microsoft Corp.",
+            ["AC5E8A"] = "Microsoft Corp.",
+            ["B0A777"] = "Microsoft Corp.",
+            ["B48AF0"] = "Microsoft Corp.",
+            ["B849A7"] = "Microsoft Corp.",
+            ["BC80CB"] = "Microsoft Corp.",
+            ["C04C7F"] = "Microsoft Corp.",
+            ["C8D5FE"] = "Microsoft Corp.",
+            ["CC44D6"] = "Microsoft Corp.",
+            ["D069D0"] = "Microsoft Corp.",
+            ["D4A64B"] = "Microsoft Corp.",
+            ["D833B2"] = "Microsoft Corp.",
+            ["DC0C5C"] = "Microsoft Corp.",
+            ["E09D36"] = "Microsoft Corp.",
+            ["E0AE32"] = "Microsoft Corp.",
+            ["E46E7B"] = "Microsoft Corp.",
+            ["E8B8AA"] = "Microsoft Corp.",
+            ["EC80F4"] = "Microsoft Corp.",
+            ["F0D5BF"] = "Microsoft Corp.",
+            ["F48E92"] = "Microsoft Corp.",
+            ["FC6C31"] = "Microsoft Corp.",
+
+            // ── Sony ──
+            ["001583"] = "Sony Corp.",
+            ["001DBA"] = "Sony Corp.",
+            ["002454"] = "Sony Corp.",
+            ["006009"] = "Sony Corp.",
+            ["080046"] = "Sony Corp.",
+            ["0C191A"] = "Sony Corp.",
+            ["14A383"] = "Sony Corp.",
+            ["1CC073"] = "Sony Corp.",
+            ["2461A8"] = "Sony Corp.",
+            ["2CD290"] = "Sony Corp.",
+            ["3046F9"] = "Sony Corp.",
+            ["4021B8"] = "Sony Corp.",
+            ["48050B"] = "Sony Corp.",
+            ["501F8B"] = "Sony Corp.",
+            ["5C99B4"] = "Sony Corp.",
+            ["6015BE"] = "Sony Corp.",
+            ["64B678"] = "Sony Corp.",
+            ["70DB98"] = "Sony Corp.",
+            ["74DBD1"] = "Sony Corp.",
+            ["7C1CF9"] = "Sony Corp.",
+            ["8056F2"] = "Sony Corp.",
+            ["84B5B4"] = "Sony Corp.",
+            ["8C81F9"] = "Sony Corp.",
+            ["9024CA"] = "Sony Corp.",
+            ["949ABB"] = "Sony Corp.",
+            ["9C2DCD"] = "Sony Corp.",
+            ["A036F0"] = "Sony Corp.",
+            ["A47733"] = "Sony Corp.",
+            ["AC3F72"] = "Sony Corp.",
+            ["B0E23A"] = "Sony Corp.",
+            ["B48655"] = "Sony Corp.",
+            ["BCB851"] = "Sony Corp.",
+            ["C0741A"] = "Sony Corp.",
+            ["C4B6A1"] = "Sony Corp.",
+            ["CC29BB"] = "Sony Corp.",
+            ["D0131E"] = "Sony Corp.",
+            ["D4F0B4"] = "Sony Corp.",
+            ["DC2EFE"] = "Sony Corp.",
+            ["E0A74B"] = "Sony Corp.",
+            ["E44E8F"] = "Sony Corp.",
+            ["E8B4C8"] = "Sony Corp.",
+            ["EC8EAD"] = "Sony Corp.",
+            ["F09A52"] = "Sony Corp.",
+            ["F425CB"] = "Sony Corp.",
+            ["F87A1B"] = "Sony Corp.",
+            ["FC03AE"] = "Sony Corp.",
+
+            // ── LG Electronics ──
+            ["00163E"] = "LG Electronics",
+            ["001CF0"] = "LG Electronics",
+            ["00233A"] = "LG Electronics",
+            ["00287C"] = "LG Electronics",
+            ["0030D9"] = "LG Electronics",
+            ["0080FB"] = "LG Electronics",
+            ["00C0E6"] = "LG Electronics",
+            ["0800C0"] = "LG Electronics",
+            ["0C4A00"] = "LG Electronics",
+            ["142123"] = "LG Electronics",
+            ["184216"] = "LG Electronics",
+            ["1C5A3A"] = "LG Electronics",
+            ["2003ED"] = "LG Electronics",
+            ["24DBAC"] = "LG Electronics",
+            ["285B2E"] = "LG Electronics",
+            ["2C1B08"] = "LG Electronics",
+            ["3044C9"] = "LG Electronics",
+            ["342DAC"] = "LG Electronics",
+            ["3801B9"] = "LG Electronics",
+            ["3CF748"] = "LG Electronics",
+            ["4014B6"] = "LG Electronics",
+            ["4460E0"] = "LG Electronics",
+            ["48137D"] = "LG Electronics",
+            ["4C0B3A"] = "LG Electronics",
+            ["500B32"] = "LG Electronics",
+            ["540182"] = "LG Electronics",
+            ["5858F0"] = "LG Electronics",
+            ["5C0E5D"] = "LG Electronics",
+            ["603B35"] = "LG Electronics",
+            ["647FA1"] = "LG Electronics",
+            ["68E9E0"] = "LG Electronics",
+            ["6C2E1A"] = "LG Electronics",
+            ["6C9F6C"] = "LG Electronics",
+            ["7041B7"] = "LG Electronics",
+            ["74894A"] = "LG Electronics",
+            ["7C11A5"] = "LG Electronics",
+            ["7C5065"] = "LG Electronics",
+            ["8002DF"] = "LG Electronics",
+            ["84FD27"] = "LG Electronics",
+            ["883C6D"] = "LG Electronics",
+            ["8C404A"] = "LG Electronics",
+            ["90014C"] = "LG Electronics",
+            ["94D72E"] = "LG Electronics",
+            ["98BE6E"] = "LG Electronics",
+            ["9C2A70"] = "LG Electronics",
+            ["A0C493"] = "LG Electronics",
+            ["A4530E"] = "LG Electronics",
+            ["ACD64D"] = "LG Electronics",
+            ["B0824C"] = "LG Electronics",
+            ["B4DE3D"] = "LG Electronics",
+            ["B880F1"] = "LG Electronics",
+            ["BC0543"] = "LG Electronics",
+            ["C071DE"] = "LG Electronics",
+            ["C4B512"] = "LG Electronics",
+            ["C85BFC"] = "LG Electronics",
+            ["CC27A2"] = "LG Electronics",
+            ["D0CCE1"] = "LG Electronics",
+            ["D4789B"] = "LG Electronics",
+            ["D8DE78"] = "LG Electronics",
+            ["DC0B7C"] = "LG Electronics",
+            ["E048BA"] = "LG Electronics",
+            ["E0FC71"] = "LG Electronics",
+            ["E44919"] = "LG Electronics",
+            ["E8B2FD"] = "LG Electronics",
+            ["EC64E9"] = "LG Electronics",
+            ["F05B3A"] = "LG Electronics",
+            ["F4B2C0"] = "LG Electronics",
+            ["F87B1E"] = "LG Electronics",
+
+            // ── Lenovo ──
+            ["0003AB"] = "Lenovo Group",
+            ["000BF6"] = "Lenovo Group",
+            ["001405"] = "Lenovo Group",
+            ["0015E1"] = "Lenovo Group",
+            ["0019D2"] = "Lenovo Group",
+            ["001E85"] = "Lenovo Group",
+            ["002166"] = "Lenovo Group",
+            ["00244E"] = "Lenovo Group",
+            ["0025EB"] = "Lenovo Group",
+            ["002984"] = "Lenovo Group",
+            ["0040F4"] = "Lenovo Group",
+            ["005062"] = "Lenovo Group",
+            ["0821EF"] = "Lenovo Group",
+            ["0C4CE1"] = "Lenovo Group",
+            ["142CD4"] = "Lenovo Group",
+            ["1CAB01"] = "Lenovo Group",
+            ["246968"] = "Lenovo Group",
+            ["2C036D"] = "Lenovo Group",
+            ["30D9D6"] = "Lenovo Group",
+            ["34A295"] = "Lenovo Group",
+            ["3C6F4B"] = "Lenovo Group",
+            ["40984E"] = "Lenovo Group",
+            ["447E75"] = "Lenovo Group",
+            ["4C4B68"] = "Lenovo Group",
+            ["50CA97"] = "Lenovo Group",
+            ["582BDB"] = "Lenovo Group",
+            ["5C518D"] = "Lenovo Group",
+            ["6083B2"] = "Lenovo Group",
+            ["647BD4"] = "Lenovo Group",
+            ["68F728"] = "Lenovo Group",
+            ["6C388B"] = "Lenovo Group",
+            ["70C639"] = "Lenovo Group",
+            ["784CB8"] = "Lenovo Group",
+            ["7C62E9"] = "Lenovo Group",
+            ["809C63"] = "Lenovo Group",
+            ["843EE4"] = "Lenovo Group",
+            ["88AE07"] = "Lenovo Group",
+            ["8CEEC4"] = "Lenovo Group",
+            ["90E6BA"] = "Lenovo Group",
+            ["94C78B"] = "Lenovo Group",
+            ["98CB27"] = "Lenovo Group",
+            ["9C4E30"] = "Lenovo Group",
+            ["A0AAFD"] = "Lenovo Group",
+            ["A48D3B"] = "Lenovo Group",
+            ["AC8E8B"] = "Lenovo Group",
+            ["B05D8C"] = "Lenovo Group",
+            ["B4B31D"] = "Lenovo Group",
+            ["B8C890"] = "Lenovo Group",
+            ["BC44F2"] = "Lenovo Group",
+            ["C06C6F"] = "Lenovo Group",
+            ["C436DA"] = "Lenovo Group",
+            ["C87D2E"] = "Lenovo Group",
+            ["CC3A5E"] = "Lenovo Group",
+            ["D05A44"] = "Lenovo Group",
+            ["D0C789"] = "Lenovo Group",
+            ["D413F5"] = "Lenovo Group",
+            ["D80D36"] = "Lenovo Group",
+            ["DC164E"] = "Lenovo Group",
+            ["E062D4"] = "Lenovo Group",
+            ["E49A44"] = "Lenovo Group",
+            ["E8A0C3"] = "Lenovo Group",
+            ["EC091C"] = "Lenovo Group",
+            ["F01898"] = "Lenovo Group",
+            ["F065DD"] = "Lenovo Group",
+            ["F43318"] = "Lenovo Group",
+            ["F4B1C2"] = "Lenovo Group",
+            ["F8142E"] = "Lenovo Group",
+            ["FCB643"] = "Lenovo Group",
+
+            // ── Dell ──
+            ["0000FE"] = "Dell Inc.",
+            ["001018"] = "Dell Inc.",
+            ["0012A9"] = "Dell Inc.",
+            ["00144F"] = "Dell Inc.",
+            ["001681"] = "Dell Inc.",
+            ["0018D3"] = "Dell Inc.",
+            ["001A68"] = "Dell Inc.",
+            ["001C23"] = "Dell Inc.",
+            ["001E4F"] = "Dell Inc.",
+            ["0020B1"] = "Dell Inc.",
+            ["00216A"] = "Dell Inc.",
+            ["0023AE"] = "Dell Inc.",
+            ["00224B"] = "Dell Inc.",
+            ["00245A"] = "Dell Inc.",
+            ["0025B3"] = "Dell Inc.",
+            ["0026E2"] = "Dell Inc.",
+            ["0027E6"] = "Dell Inc.",
+            ["00296E"] = "Dell Inc.",
+            ["005043"] = "Dell Inc.",
+            ["00B5D6"] = "Dell Inc.",
+            ["00C0F1"] = "Dell Inc.",
+            ["00E04C"] = "Dell Inc.",
+            ["00ED19"] = "Dell Inc.",
+            ["0417F2"] = "Dell Inc.",
+            ["0C1D56"] = "Dell Inc.",
+            ["10A4BE"] = "Dell Inc.",
+            ["183D9C"] = "Dell Inc.",
+            ["1C484B"] = "Dell Inc.",
+            ["241BEB"] = "Dell Inc.",
+            ["2C39C1"] = "Dell Inc.",
+            ["30334F"] = "Dell Inc.",
+            ["3473DE"] = "Dell Inc.",
+            ["380FE4"] = "Dell Inc.",
+            ["3C2C30"] = "Dell Inc.",
+            ["403DDB"] = "Dell Inc.",
+            ["4451DB"] = "Dell Inc.",
+            ["48D705"] = "Dell Inc.",
+            ["4CEB42"] = "Dell Inc.",
+            ["508245"] = "Dell Inc.",
+            ["54271B"] = "Dell Inc.",
+            ["58B961"] = "Dell Inc.",
+            ["5C654D"] = "Dell Inc.",
+            ["6073BC"] = "Dell Inc.",
+            ["645E3C"] = "Dell Inc.",
+            ["68B66B"] = "Dell Inc.",
+            ["6C626D"] = "Dell Inc.",
+            ["7081BD"] = "Dell Inc.",
+            ["746274"] = "Dell Inc.",
+            ["78ACC0"] = "Dell Inc.",
+            ["7C0B6A"] = "Dell Inc.",
+            ["7CE044"] = "Dell Inc.",
+            ["8063B5"] = "Dell Inc.",
+            ["843C3B"] = "Dell Inc.",
+            ["880B6D"] = "Dell Inc.",
+            ["8C271D"] = "Dell Inc.",
+            ["905090"] = "Dell Inc.",
+            ["94B06B"] = "Dell Inc.",
+            ["98F7E5"] = "Dell Inc.",
+            ["9C060C"] = "Dell Inc.",
+            ["9CFC01"] = "Dell Inc.",
+            ["A029F4"] = "Dell Inc.",
+            ["A4726D"] = "Dell Inc.",
+            ["A8A159"] = "Dell Inc.",
+            ["AC814A"] = "Dell Inc.",
+            ["B078F0"] = "Dell Inc.",
+            ["B4B65F"] = "Dell Inc.",
+            ["B8AC6F"] = "Dell Inc.",
+            ["BCEA2B"] = "Dell Inc.",
+            ["C09F42"] = "Dell Inc.",
+            ["C4F148"] = "Dell Inc.",
+            ["C8B5AD"] = "Dell Inc.",
+            ["CCB8F1"] = "Dell Inc.",
+            ["D0C0BF"] = "Dell Inc.",
+            ["D42C3D"] = "Dell Inc.",
+            ["D4AE52"] = "Dell Inc.",
+            ["D88648"] = "Dell Inc.",
+            ["DC4BEB"] = "Dell Inc.",
+            ["E01876"] = "Dell Inc.",
+            ["E0CC7A"] = "Dell Inc.",
+            ["E07C9C"] = "Dell Inc.",
+            ["E4297F"] = "Dell Inc.",
+            ["E89D87"] = "Dell Inc.",
+            ["EC3091"] = "Dell Inc.",
+            ["F03D29"] = "Dell Inc.",
+            ["F44D30"] = "Dell Inc.",
+            ["F81EDF"] = "Dell Inc.",
+            ["FC5C45"] = "Dell Inc.",
+            ["FCE557"] = "Dell Inc.",
+            ["14B455"] = "Dell Inc.",
+            ["80763F"] = "Dell Inc.",
+            ["48D224"] = "Dell Inc.",
+            ["90A42C"] = "Dell Inc.",
+            ["B8621F"] = "Dell Inc.",
+
+            // ── HP ──
+            ["0004EA"] = "HP Inc.",
+            ["0008C7"] = "HP Inc.",
+            ["000FE8"] = "HP Inc.",
+            ["001279"] = "HP Inc.",
+            ["00141B"] = "HP Inc.",
+            ["001560"] = "HP Inc.",
+            ["001635"] = "HP Inc.",
+            ["001871"] = "HP Inc.",
+            ["001EBD"] = "HP Inc.",
+            ["00215A"] = "HP Inc.",
+            ["002264"] = "HP Inc.",
+            ["00237D"] = "HP Inc.",
+            ["0024A5"] = "HP Inc.",
+            ["00254B"] = "HP Inc.",
+            ["0026AE"] = "HP Inc.",
+            ["0026C6"] = "HP Inc.",
+            ["002780"] = "HP Inc.",
+            ["002833"] = "HP Inc.",
+            ["00C0F1"] = "HP Inc.",
+            ["00E08C"] = "HP Inc.",
+            ["00E0F8"] = "HP Inc.",
+            ["0C502A"] = "HP Inc.",
+            ["10C57D"] = "HP Inc.",
+            ["147526"] = "HP Inc.",
+            ["1830DC"] = "HP Inc.",
+            ["1C17D3"] = "HP Inc.",
+            ["1C3E8D"] = "HP Inc.",
+            ["204B34"] = "HP Inc.",
+            ["24023C"] = "HP Inc.",
+            ["2806B9"] = "HP Inc.",
+            ["2CCD27"] = "HP Inc.",
+            ["300ED1"] = "HP Inc.",
+            ["3451AE"] = "HP Inc.",
+            ["38E2DD"] = "HP Inc.",
+            ["3C0D9B"] = "HP Inc.",
+            ["3CD695"] = "HP Inc.",
+            ["4001C6"] = "HP Inc.",
+            ["4437E6"] = "HP Inc.",
+            ["4813D3"] = "HP Inc.",
+            ["4C35CF"] = "HP Inc.",
+            ["504AAC"] = "HP Inc.",
+            ["5453ED"] = "HP Inc.",
+            ["585F64"] = "HP Inc.",
+            ["5C18B8"] = "HP Inc.",
+            ["60109B"] = "HP Inc.",
+            ["64A2F9"] = "HP Inc.",
+            ["682B6C"] = "HP Inc.",
+            ["6C3C59"] = "HP Inc.",
+            ["704D1A"] = "HP Inc.",
+            ["748C42"] = "HP Inc.",
+            ["78171B"] = "HP Inc.",
+            ["7C11BE"] = "HP Inc.",
+            ["7C7D41"] = "HP Inc.",
+            ["80D05A"] = "HP Inc.",
+            ["8400D2"] = "HP Inc.",
+            ["881EAE"] = "HP Inc.",
+            ["8C4CDE"] = "HP Inc.",
+            ["90568C"] = "HP Inc.",
+            ["943194"] = "HP Inc.",
+            ["9866EA"] = "HP Inc.",
+            ["9C2923"] = "HP Inc.",
+            ["A04D43"] = "HP Inc.",
+            ["A05815"] = "HP Inc.",
+            ["A449CF"] = "HP Inc.",
+            ["A89D6A"] = "HP Inc.",
+            ["ACB158"] = "HP Inc.",
+            ["B0C69A"] = "HP Inc.",
+            ["B49089"] = "HP Inc.",
+            ["BC5498"] = "HP Inc.",
+            ["C0C1C0"] = "HP Inc.",
+            ["C4D214"] = "HP Inc.",
+            ["C8C5BB"] = "HP Inc.",
+            ["CC2D21"] = "HP Inc.",
+            ["D05F64"] = "HP Inc.",
+            ["D0C28A"] = "HP Inc.",
+            ["D093F8"] = "HP Inc.",
+            ["D49C8E"] = "HP Inc.",
+            ["D8C46D"] = "HP Inc.",
+            ["DC7BA4"] = "HP Inc.",
+            ["E032F3"] = "HP Inc.",
+            ["E45D06"] = "HP Inc.",
+            ["E8DAAA"] = "HP Inc.",
+            ["EC2E4E"] = "HP Inc.",
+            ["F03F49"] = "HP Inc.",
+            ["F44B1A"] = "HP Inc.",
+            ["F80BF5"] = "HP Inc.",
+            ["FC86CF"] = "HP Inc.",
+
+            // ── ASUS ──
+            ["000E6A"] = "ASUSTek Computer",
+            ["0015F2"] = "ASUSTek Computer",
+            ["001E8C"] = "ASUSTek Computer",
+            ["0022B0"] = "ASUSTek Computer",
+            ["00248C"] = "ASUSTek Computer",
+            ["00269A"] = "ASUSTek Computer",
+            ["0026AB"] = "ASUSTek Computer",
+            ["005091"] = "ASUSTek Computer",
+            ["00605C"] = "ASUSTek Computer",
+            ["0475F5"] = "ASUSTek Computer",
+            ["080D84"] = "ASUSTek Computer",
+            ["0C137D"] = "ASUSTek Computer",
+            ["0C9D92"] = "ASUSTek Computer",
+            ["141A67"] = "ASUSTek Computer",
+            ["145A05"] = "ASUSTek Computer",
+            ["18A99B"] = "ASUSTek Computer",
+            ["1C872C"] = "ASUSTek Computer",
+            ["1CB72C"] = "ASUSTek Computer",
+            ["201289"] = "ASUSTek Computer",
+            ["24BF65"] = "ASUSTek Computer",
+            ["28EBBA"] = "ASUSTek Computer",
+            ["2C10C1"] = "ASUSTek Computer",
+            ["2CF0A2"] = "ASUSTek Computer",
+            ["300562"] = "ASUSTek Computer",
+            ["3476C5"] = "ASUSTek Computer",
+            ["387482"] = "ASUSTek Computer",
+            ["3C5436"] = "ASUSTek Computer",
+            ["3C7B41"] = "ASUSTek Computer",
+            ["4091B1"] = "ASUSTek Computer",
+            ["40B2C8"] = "ASUSTek Computer",
+            ["448C34"] = "ASUSTek Computer",
+            ["4497F5"] = "ASUSTek Computer",
+            ["485268"] = "ASUSTek Computer",
+            ["4C0289"] = "ASUSTek Computer",
+            ["5097AA"] = "ASUSTek Computer",
+            ["5463B1"] = "ASUSTek Computer",
+            ["5866BA"] = "ASUSTek Computer",
+            ["5C3A3E"] = "ASUSTek Computer",
+            ["6045CB"] = "ASUSTek Computer",
+            ["64B310"] = "ASUSTek Computer",
+            ["6875CC"] = "ASUSTek Computer",
+            ["6C4B90"] = "ASUSTek Computer",
+            ["6C9459"] = "ASUSTek Computer",
+            ["704C6C"] = "ASUSTek Computer",
+            ["748BD0"] = "ASUSTek Computer",
+            ["78B64B"] = "ASUSTek Computer",
+            ["7C52D8"] = "ASUSTek Computer",
+            ["7C6B33"] = "ASUSTek Computer",
+            ["80EB77"] = "ASUSTek Computer",
+            ["84579B"] = "ASUSTek Computer",
+            ["88F390"] = "ASUSTek Computer",
+            ["8C604F"] = "ASUSTek Computer",
+            ["8CBEAE"] = "ASUSTek Computer",
+            ["9017AC"] = "ASUSTek Computer",
+            ["940469"] = "ASUSTek Computer",
+            ["94B16B"] = "ASUSTek Computer",
+            ["986B3D"] = "ASUSTek Computer",
+            ["98B6E9"] = "ASUSTek Computer",
+            ["9C456A"] = "ASUSTek Computer",
+            ["A0A763"] = "ASUSTek Computer",
+            ["A471C9"] = "ASUSTek Computer",
+            ["A8A795"] = "ASUSTek Computer",
+            ["AC6033"] = "ASUSTek Computer",
+            ["B0AA36"] = "ASUSTek Computer",
+            ["B082F9"] = "ASUSTek Computer",
+            ["B48910"] = "ASUSTek Computer",
+            ["B8AF67"] = "ASUSTek Computer",
+            ["BC8301"] = "ASUSTek Computer",
+            ["C06599"] = "ASUSTek Computer",
+            ["C0B5B1"] = "ASUSTek Computer",
+            ["C40528"] = "ASUSTek Computer",
+            ["C45C98"] = "ASUSTek Computer",
+            ["C818A5"] = "ASUSTek Computer",
+            ["CC02E5"] = "ASUSTek Computer",
+            ["D09704"] = "ASUSTek Computer",
+            ["D4AE52"] = "ASUSTek Computer",
+            ["D85D4B"] = "ASUSTek Computer",
+            ["DCA632"] = "ASUSTek Computer",
+            ["E002A1"] = "ASUSTek Computer",
+            ["E03E02"] = "ASUSTek Computer",
+            ["E09DB8"] = "ASUSTek Computer",
+            ["E0E1A9"] = "ASUSTek Computer",
+            ["E48C94"] = "ASUSTek Computer",
+            ["E88B4D"] = "ASUSTek Computer",
+            ["EC22B5"] = "ASUSTek Computer",
+            ["F01A72"] = "ASUSTek Computer",
+            ["F0ACD7"] = "ASUSTek Computer",
+            ["F407B4"] = "ASUSTek Computer",
+            ["F472E2"] = "ASUSTek Computer",
+            ["F81D93"] = "ASUSTek Computer",
+            ["F8AC6D"] = "ASUSTek Computer",
+            ["FC17B8"] = "ASUSTek Computer",
+            ["FC316E"] = "ASUSTek Computer",
+            ["FCB6D8"] = "ASUSTek Computer",
+
+            // ── Other common ──
+            ["00037F"] = "Canon Inc.",
+            ["00106B"] = "Canon Inc.",
+            ["001583"] = "Canon Inc.",
+            ["001BEF"] = "Canon Inc.",
+            ["00E001"] = "Canon Inc.",
+            ["041B94"] = "Canon Inc.",
+            ["08279C"] = "Canon Inc.",
+            ["0C9D6F"] = "Canon Inc.",
+            ["1C3E0C"] = "Canon Inc.",
+            ["243A26"] = "Canon Inc.",
+            ["34A560"] = "Canon Inc.",
+            ["404A03"] = "Canon Inc.",
+            ["4CF910"] = "Canon Inc.",
+            ["54DF63"] = "Canon Inc.",
+            ["68E42C"] = "Canon Inc.",
+            ["6C54A5"] = "Canon Inc.",
+            ["704C6C"] = "Canon Inc.",
+            ["88BFD0"] = "Canon Inc.",
+            ["8CA47C"] = "Canon Inc.",
+            ["90D112"] = "Canon Inc.",
+            ["94DE0E"] = "Canon Inc.",
+            ["A4DA3F"] = "Canon Inc.",
+            ["A83B76"] = "Canon Inc.",
+            ["B0C0EF"] = "Canon Inc.",
+            ["B8B79C"] = "Canon Inc.",
+            ["C08AA7"] = "Canon Inc.",
+            ["C49802"] = "Canon Inc.",
+            ["CC3C2F"] = "Canon Inc.",
+            ["D04944"] = "Canon Inc.",
+            ["D4C6BF"] = "Canon Inc.",
+            ["DC0820"] = "Canon Inc.",
+            ["E062D4"] = "Canon Inc.",
+            ["E06599"] = "Canon Inc.",
+            ["E4DA39"] = "Canon Inc.",
+            ["EC44F6"] = "Canon Inc.",
+            ["F0F61C"] = "Canon Inc.",
+            ["F47403"] = "Canon Inc.",
+            ["F4A889"] = "Canon Inc.",
+            ["FC28B6"] = "Canon Inc.",
+
+            ["0004F2"] = "Epson Corp.",
+            ["00115B"] = "Epson Corp.",
+            ["0014C2"] = "Epson Corp.",
+            ["001B50"] = "Epson Corp.",
+            ["001F18"] = "Epson Corp.",
+            ["00207F"] = "Epson Corp.",
+            ["00306E"] = "Epson Corp.",
+            ["004089"] = "Epson Corp.",
+            ["00A069"] = "Epson Corp.",
+            ["00D0D3"] = "Epson Corp.",
+            ["04A337"] = "Epson Corp.",
+            ["08AAD5"] = "Epson Corp.",
+            ["0C3A1F"] = "Epson Corp.",
+            ["14307A"] = "Epson Corp.",
+            ["1C3E2D"] = "Epson Corp.",
+            ["3462A5"] = "Epson Corp.",
+            ["3CC0FA"] = "Epson Corp.",
+            ["40CCB0"] = "Epson Corp.",
+            ["442A14"] = "Epson Corp.",
+            ["446D57"] = "Epson Corp.",
+            ["48212B"] = "Epson Corp.",
+            ["4C728E"] = "Epson Corp.",
+            ["5C9AD8"] = "Epson Corp.",
+            ["641FFF"] = "Epson Corp.",
+            ["680ADB"] = "Epson Corp.",
+            ["6CF086"] = "Epson Corp.",
+            ["700A3B"] = "Epson Corp.",
+            ["78807A"] = "Epson Corp.",
+            ["7C25D1"] = "Epson Corp.",
+            ["8065E9"] = "Epson Corp.",
+            ["84A810"] = "Epson Corp.",
+            ["888322"] = "Epson Corp.",
+            ["8C8CA6"] = "Epson Corp.",
+            ["9440C4"] = "Epson Corp.",
+            ["98D2CF"] = "Epson Corp.",
+            ["A01D48"] = "Epson Corp.",
+            ["A4101B"] = "Epson Corp.",
+            ["A80CA7"] = "Epson Corp.",
+            ["B09D1E"] = "Epson Corp.",
+            ["B8E76D"] = "Epson Corp.",
+            ["C4CDB2"] = "Epson Corp.",
+            ["C88DCA"] = "Epson Corp.",
+            ["CC7D6D"] = "Epson Corp.",
+            ["D0680A"] = "Epson Corp.",
+            ["D436DB"] = "Epson Corp.",
+            ["D86BF7"] = "Epson Corp.",
+            ["DC3D62"] = "Epson Corp.",
+            ["E09153"] = "Epson Corp.",
+            ["E8B880"] = "Epson Corp.",
+            ["EC2816"] = "Epson Corp.",
+            ["F0D6B7"] = "Epson Corp.",
+            ["F47315"] = "Epson Corp.",
+            ["FC4F12"] = "Epson Corp.",
+
+            ["0001E6"] = "NVIDIA Corp.",
+            ["001EEA"] = "NVIDIA Corp.",
+            ["0026B3"] = "NVIDIA Corp.",
+            ["080030"] = "NVIDIA Corp.",
+            ["1407E0"] = "NVIDIA Corp.",
+            ["1C694E"] = "NVIDIA Corp.",
+            ["247703"] = "NVIDIA Corp.",
+            ["2C3BFD"] = "NVIDIA Corp.",
+            ["30F9ED"] = "NVIDIA Corp.",
+            ["48E3AB"] = "NVIDIA Corp.",
+            ["4C5FF9"] = "NVIDIA Corp.",
+            ["50EB1A"] = "NVIDIA Corp.",
+            ["60CBFB"] = "NVIDIA Corp.",
+            ["64B853"] = "NVIDIA Corp.",
+            ["6C2B59"] = "NVIDIA Corp.",
+            ["7888B0"] = "NVIDIA Corp.",
+            ["7C6690"] = "NVIDIA Corp.",
+            ["8C89A5"] = "NVIDIA Corp.",
+            ["984BE1"] = "NVIDIA Corp.",
+            ["A03394"] = "NVIDIA Corp.",
+            ["A0F6FD"] = "NVIDIA Corp.",
+            ["B0A44D"] = "NVIDIA Corp.",
+            ["B47439"] = "NVIDIA Corp.",
+            ["B895ED"] = "NVIDIA Corp.",
+            ["BCEF8F"] = "NVIDIA Corp.",
+            ["C006B7"] = "NVIDIA Corp.",
+            ["C807D3"] = "NVIDIA Corp.",
+            ["CC701F"] = "NVIDIA Corp.",
+            ["D46A40"] = "NVIDIA Corp.",
+            ["D8CAA6"] = "NVIDIA Corp.",
+            ["E04F43"] = "NVIDIA Corp.",
+            ["E4789D"] = "NVIDIA Corp.",
+            ["E8F928"] = "NVIDIA Corp.",
+            ["F0D5BF"] = "NVIDIA Corp.",
+            ["F4A28C"] = "NVIDIA Corp.",
+            ["F8A29C"] = "NVIDIA Corp.",
+        };
+    }
+}
Modified +42 -1
diff --git a/Rainbow.Data/Services/ShellAuditLogger.cs b/Rainbow.Data/Services/ShellAuditLogger.cs
index 276acce..255829e 100644
--- a/Rainbow.Data/Services/ShellAuditLogger.cs
+++ b/Rainbow.Data/Services/ShellAuditLogger.cs
@@ -1,23 +1,64 @@
 using NewLife.Log;
+// NetworkLog via fully qualified name to avoid ambiguity with NewLife.Log.NetworkLog
 
 namespace Rainbow.Services;
 
-/// <summary>Shell 审计日志记录器</summary>
+/// <summary>Shell 审计日志记录器。写入文本日志并持久化到 NetworkLog 数据库表</summary>
 public class ShellAuditLogger
 {
     /// <summary>日志</summary>
     public NewLife.Log.ILog Logger { get; set; }
 
+    /// <summary>是否启用数据库审计写入。默认 true</summary>
+    public Boolean EnableDbAudit { get; set; } = true;
+
     /// <summary>实例化</summary>
     public ShellAuditLogger()
     {
         Logger = NewLife.Log.Logger.Null;
     }
+
     /// <summary>写入审计日志</summary>
     /// <param name="result">Shell 执行结果,含命令/参数/退出码/耗时</param>
     public void Write(ShellResult result)
     {
         if (result == null) return;
+
+        // 文本日志
         Logger.Info($"[ShellAudit] {result.Command} {result.Arguments} | ExitCode={result.ExitCode} | {result.Elapsed}ms");
+
+        // 持久化到 NetworkLog 表
+        if (EnableDbAudit)
+            WriteToDatabase(result);
+    }
+
+    private void WriteToDatabase(ShellResult result)
+    {
+        try
+        {
+            // 从命令名推导 Action 前缀
+            var cmd = result.Command ?? "";
+            var action = cmd.IndexOf('/') >= 0 ? cmd[(cmd.LastIndexOf('/') + 1)..] : cmd;
+            if (action.Length > 50) action = action[..50];
+
+            var log = new Rainbow.Entity.NetworkLog
+            {
+                Action = $"Shell.{action}",
+                Command = result.Command,
+                Arguments = result.Arguments?.Length > 1000 ? result.Arguments[..1000] : result.Arguments,
+                ExitCode = result.ExitCode,
+                Stdout = result.Stdout?.Length > 5000 ? result.Stdout[..5000] : result.Stdout,
+                Stderr = result.Stderr?.Length > 2000 ? result.Stderr[..2000] : result.Stderr,
+                Elapsed = (Int32)result.Elapsed,
+                Success = result.Success,
+                Enable = true,
+                CreateTime = DateTime.Now
+            };
+            log.Insert();
+        }
+        catch (Exception ex)
+        {
+            Logger.Error("NetworkLog 写入失败:{0}", ex.Message);
+        }
     }
 }
Modified +16 -1
diff --git a/Rainbow.Web/Controllers/DeviceController.cs b/Rainbow.Web/Controllers/DeviceController.cs
index cea13d8..16c0475 100644
--- a/Rainbow.Web/Controllers/DeviceController.cs
+++ b/Rainbow.Web/Controllers/DeviceController.cs
@@ -13,10 +13,16 @@ namespace Rainbow.Web.Controllers;
 public class DeviceController : ControllerBase
 {
     private readonly DeviceBlockService _blockService;
+    private readonly OuiLookupService _ouiLookup;
 
     /// <summary>构造设备管理控制器</summary>
     /// <param name="blockService">设备阻断服务(防火墙联动 + 状态同步)</param>
-    public DeviceController(DeviceBlockService blockService) => _blockService = blockService;
+    /// <param name="ouiLookup">OUI 厂商识别服务</param>
+    public DeviceController(DeviceBlockService blockService, OuiLookupService ouiLookup)
+    {
+        _blockService = blockService;
+        _ouiLookup = ouiLookup;
+    }
 
     /// <summary>获取所有设备列表</summary>
     /// <returns>设备列表</returns>
@@ -24,6 +30,12 @@ public class DeviceController : ControllerBase
     public ActionResult<IList<Device>> GetDevices()
     {
         var list = Device.FindAllWithCache();
+        // 懒填充厂商信息
+        foreach (var device in list)
+        {
+            if (device.Vendor.IsNullOrEmpty() && !device.Mac.IsNullOrEmpty())
+                _ouiLookup.TrySetVendor(device.Mac, device);
+        }
         return Ok(list);
     }
 
@@ -35,6 +47,9 @@ public class DeviceController : ControllerBase
     {
         var device = Device.FindById(id);
         if (device == null) return NotFound(new { error = "设备不存在" });
+        // 懒填充厂商信息
+        if (device.Vendor.IsNullOrEmpty() && !device.Mac.IsNullOrEmpty())
+            _ouiLookup.TrySetVendor(device.Mac, device);
         return Ok(device);
     }
 
Modified +6 -0
diff --git a/Rainbow.Web/Program.cs b/Rainbow.Web/Program.cs
index 057304d..85d56b6 100644
--- a/Rainbow.Web/Program.cs
+++ b/Rainbow.Web/Program.cs
@@ -42,6 +42,9 @@ public partial class Program
         var osAdapter = AdapterFactory.Create();
         services.AddSingleton<IOSAdapter>(osAdapter);
 
+        // OUI 厂商识别服务
+        services.AddSingleton<OuiLookupService>();
+
         // 网络管理接口(按平台注册具体实现)
         services.AddSingleton<INetworkInterfaceManager>(sp =>
         {
@@ -115,6 +118,9 @@ public partial class Program
         services.AddSingleton<DeviceBlockService>();
         services.AddSingleton<FamilyScheduleService>();
 
+        // DNS-over-HTTPS 转发器(当上游配置 DOH 时启用)
+        services.AddSingleton<DnsOverHttpsForwarder>();
+
         // 拦截提示页服务(端口 9080)
         services.AddHostedService<BlockPageService>();
 
Modified +182 -0
diff --git a/XUnitTest/Rainbow.Services/DnsLogParserTests.cs b/XUnitTest/Rainbow.Services/DnsLogParserTests.cs
index 9a85b9f..482a320 100644
--- a/XUnitTest/Rainbow.Services/DnsLogParserTests.cs
+++ b/XUnitTest/Rainbow.Services/DnsLogParserTests.cs
@@ -3,6 +3,7 @@ using Xunit;
 
 namespace XUnitTest.Rainbow.Services;
 
+/// <summary>DnsLogParser 单元测试。覆盖日志解析、域名匹配、通配/正则模式</summary>
 public class DnsLogParserTests
 {
     [Fact(DisplayName = "DnsLogParser_构造_创建成功")]
@@ -29,4 +30,185 @@ public class DnsLogParserTests
         parser.Start(); // 第二次调用应跳过
         parser.Stop();
     }
+
+    // ── ExtractQueriedDomains 日志解析 ──
+
+    [Fact(DisplayName = "DnsLogParser_ExtractQueriedDomains_解析A记录")]
+    public void ExtractQueriedDomains_ParsesARecord()
+    {
+        var lines = new List<String>
+        {
+            "Jul 20 10:00:01 dnsmasq[1234]: query[A] example.com from 192.168.1.100"
+        };
+        var domains = DnsLogParser.ExtractQueriedDomains(lines);
+        Assert.Contains("example.com", domains);
+    }
+
+    [Fact(DisplayName = "DnsLogParser_ExtractQueriedDomains_解析AAAA记录")]
+    public void ExtractQueriedDomains_ParsesAAAARecord()
+    {
+        var lines = new List<String>
+        {
+            "Jul 20 10:00:01 dnsmasq[1234]: query[AAAA] ipv6.example.com from 192.168.1.100"
+        };
+        var domains = DnsLogParser.ExtractQueriedDomains(lines);
+        Assert.Contains("ipv6.example.com", domains);
+    }
+
+    [Fact(DisplayName = "DnsLogParser_ExtractQueriedDomains_解析MX记录")]
+    public void ExtractQueriedDomains_ParsesMXRecord()
+    {
+        var lines = new List<String>
+        {
+            "Jul 20 10:00:01 dnsmasq[1234]: query[MX] mail.example.com from 192.168.1.100"
+        };
+        var domains = DnsLogParser.ExtractQueriedDomains(lines);
+        Assert.Contains("mail.example.com", domains);
+    }
+
+    [Fact(DisplayName = "DnsLogParser_ExtractQueriedDomains_多行日志")]
+    public void ExtractQueriedDomains_MultipleLines()
+    {
+        var lines = new List<String>
+        {
+            "Jul 20 10:00:01 dnsmasq[1234]: query[A] example.com from 192.168.1.100",
+            "Jul 20 10:00:02 dnsmasq[1234]: query[A] google.com from 192.168.1.101",
+            "Jul 20 10:00:03 dnsmasq[1234]: cached example.org is 1.2.3.4",
+            "Jul 20 10:00:04 dnsmasq[1234]: query[AAAA] youtube.com from 192.168.1.102",
+        };
+        var domains = DnsLogParser.ExtractQueriedDomains(lines);
+        Assert.Equal(3, domains.Count);
+        Assert.Contains("example.com", domains);
+        Assert.Contains("google.com", domains);
+        Assert.Contains("youtube.com", domains);
+    }
+
+    [Fact(DisplayName = "DnsLogParser_ExtractQueriedDomains_空输入返回空")]
+    public void ExtractQueriedDomains_EmptyInput()
+    {
+        var domains = DnsLogParser.ExtractQueriedDomains([]);
+        Assert.Empty(domains);
+    }
+
+    [Fact(DisplayName = "DnsLogParser_ExtractQueriedDomains_无效行跳过")]
+    public void ExtractQueriedDomains_SkipsInvalidLines()
+    {
+        var lines = new List<String>
+        {
+            "not a dnsmasq log line",
+            "dnsmasq[1234]: some other message",
+            "random text with query[A] but no from",
+        };
+        var domains = DnsLogParser.ExtractQueriedDomains(lines);
+        Assert.Empty(domains);
+    }
+
+    // ── IsWildcardMatch 通配匹配 ──
+
+    [Theory(DisplayName = "DnsLogParser_IsWildcardMatch_精确匹配")]
+    [InlineData("example.com", "example.com", true)]
+    [InlineData("example.com", "EXAMPLE.COM", true)]
+    [InlineData("example.com", "Example.Com", true)]
+    [InlineData("example.com", "other.com", false)]
+    [InlineData("example.com", "sub.example.com", true)]  // EndsWith .example.com
+    public void IsWildcardMatch_Exact(String pattern, String domain, Boolean expected)
+    {
+        Assert.Equal(expected, DnsLogParser.IsWildcardMatch(pattern, domain));
+    }
+
+    [Theory(DisplayName = "DnsLogParser_IsWildcardMatch_星点前缀")]
+    [InlineData("*.example.com", "sub.example.com", true)]
+    [InlineData("*.example.com", "deep.sub.example.com", true)]
+    [InlineData("*.example.com", "example.com", true)]
+    [InlineData("*.example.com", "other.org", false)]
+    public void IsWildcardMatch_StarDot(String pattern, String domain, Boolean expected)
+    {
+        Assert.Equal(expected, DnsLogParser.IsWildcardMatch(pattern, domain));
+    }
+
+    [Theory(DisplayName = "DnsLogParser_IsWildcardMatch_点前缀")]
+    [InlineData(".example.com", "sub.example.com", true)]
+    [InlineData(".example.com", "deep.sub.example.com", true)]
+    [InlineData(".example.com", "example.com", false)]  // 点前缀不匹配裸域名
+    [InlineData(".example.com", "other.com", false)]
+    public void IsWildcardMatch_DotPrefix(String pattern, String domain, Boolean expected)
+    {
+        Assert.Equal(expected, DnsLogParser.IsWildcardMatch(pattern, domain));
+    }
+
+    [Theory(DisplayName = "DnsLogParser_IsWildcardMatch_边界条件")]
+    [InlineData("", "example.com", false)]
+    [InlineData("example.com", "", false)]
+    [InlineData(null, "example.com", false)]
+    [InlineData("example.com", null, false)]
+    public void IsWildcardMatch_EdgeCases(String pattern, String domain, Boolean expected)
+    {
+        Assert.Equal(expected, DnsLogParser.IsWildcardMatch(pattern, domain));
+    }
+
+    // ── s_queryRegex 正则验证 ──
+
+    [Fact(DisplayName = "DnsLogParser_QueryRegex_匹配各种记录类型")]
+    public void QueryRegex_MatchesAllRecordTypes()
+    {
+        var recordTypes = new[] { "A", "AAAA", "MX", "TXT", "CNAME", "NS", "SOA", "SRV", "PTR" };
+        foreach (var rtype in recordTypes)
+        {
+            var line = $"Jul 20 10:00:00 dnsmasq[1]: query[{rtype}] test.com from 10.0.0.1";
+            var match = DnsLogParser.s_queryRegex.Match(line);
+            Assert.True(match.Success, $"Should match {rtype}");
+            Assert.Equal("test.com", match.Groups[1].Value);
+        }
+    }
+
+    [Fact(DisplayName = "DnsLogParser_QueryRegex_不匹配非查询行")]
+    public void QueryRegex_DoesNotMatchNonQueryLines()
+    {
+        var lines = new[]
+        {
+            "Jul 20 10:00:00 dnsmasq[1]: started, version 2.90 cachesize 1024",
+            "Jul 20 10:00:01 dnsmasq[1]: compile time options: IPv6 GNU-getopt",
+            "Jul 20 10:00:02 dnsmasq[1]: reading /etc/resolv.conf",
+            "cached www.example.com is 1.2.3.4",
+            "reply www.example.com is 1.2.3.4",
+        };
+        foreach (var line in lines)
+        {
+            var match = DnsLogParser.s_queryRegex.Match(line);
+            Assert.False(match.Success, $"Should not match: {line}");
+        }
+    }
+
+    // ── 多协议混合 ──
+
+    [Fact(DisplayName = "DnsLogParser_ExtractQueriedDomains_多协议混合")]
+    public void ExtractQueriedDomains_MixedProtocols()
+    {
+        var lines = new List<String>
+        {
+            "Jul 20 10:00:00 dnsmasq[1]: query[A] a.com from 10.0.0.1",
+            "Jul 20 10:00:01 dnsmasq[1]: query[AAAA] b.com from 10.0.0.2",
+            "Jul 20 10:00:02 dnsmasq[1]: query[TXT] c.com from 10.0.0.3",
+            "Jul 20 10:00:03 dnsmasq[1]: query[CNAME] d.com from 10.0.0.4",
+        };
+        var domains = DnsLogParser.ExtractQueriedDomains(lines);
+        Assert.Equal(4, domains.Count);
+    }
+
+    // ── 域名去重 ──
+
+    [Fact(DisplayName = "DnsLogParser_ExtractQueriedDomains_保留重复域名")]
+    public void ExtractQueriedDomains_PreservesDuplicates()
+    {
+        // 解析器不做去重,保留所有查询
+        var lines = new List<String>
+        {
+            "Jul 20 10:00:00 dnsmasq[1]: query[A] example.com from 10.0.0.1",
+            "Jul 20 10:00:01 dnsmasq[1]: query[A] example.com from 10.0.0.2",
+            "Jul 20 10:00:02 dnsmasq[1]: query[A] example.com from 10.0.0.3",
+        };
+        var domains = DnsLogParser.ExtractQueriedDomains(lines);
+        Assert.Equal(3, domains.Count);
+        Assert.All(domains, d => Assert.Equal("example.com", d));
+    }
 }
Modified +1 -1
diff --git a/XUnitTest/Rainbow/Controllers/DeviceControllerTests.cs b/XUnitTest/Rainbow/Controllers/DeviceControllerTests.cs
index 7369527..567c2ba 100644
--- a/XUnitTest/Rainbow/Controllers/DeviceControllerTests.cs
+++ b/XUnitTest/Rainbow/Controllers/DeviceControllerTests.cs
@@ -9,7 +9,7 @@ namespace XUnitTest.Rainbow.Controllers;
 public class DeviceControllerTests
 {
     private static DeviceController CreateController() =>
-        new(new DeviceBlockService(new FakeFirewallAdapter()));
+        new(new DeviceBlockService(new FakeFirewallAdapter()), new OuiLookupService());
     [Fact(DisplayName = "Device_GetDevices_返回设备列表")]
     public void GetDevices_ReturnsList()
     {