如何利用NBNS协议解析同一Wi-Fi网络内设备的主机名?
如何用NBNS协议解析同WiFi网络设备的主机名(基于GCDAsyncUdpSocket)
你的现有代码问题
- 发送数据不符合NBNS规范:NBNS要求请求是严格的二进制格式数据包,不是简单的UTF-8字符串"Hi",设备收到无效请求不会响应
- 端口使用错误:你把请求发到了自己绑定的随机端口,而NBNS服务默认监听的是137端口
- 目标IP设置不合理:如果是批量查询同网段设备,应该使用子网广播地址(比如你的子网是192.168.0.x,广播地址就是192.168.0.255),而非单个设备IP(单个IP仅适用于定向查询某台设备)
NBNS查询核心要点
NBNS(NetBIOS名称服务)通过UDP 137端口通信,查询请求必须包含以下关键二进制字段:
- 事务ID:2字节随机数,用于匹配请求和响应
- 标志:2字节,查询请求一般设为0x0000
- 问题计数:2字节,设为0x0001
- 查询名称:需转换为指定格式的目标设备NetBIOS名称,后跟结束标志、查询类型(0x0020,NetBIOS主机名)和类(0x0001,IN类)
修正后的实现代码
先实现NBNS查询包生成逻辑,再调整UDP收发流程:
import Foundation import GCDAsyncUdpSocket class NBNSResolver: NSObject, GCDAsyncUdpSocketDelegate { var udpSocket: GCDAsyncUdpSocket! let nbnsPort: UInt16 = 137 // 根据你的实际WiFi子网调整广播地址 let broadcastIP: String = "192.168.0.255" func startNBNSQuery() { udpSocket = GCDAsyncUdpSocket(delegate: self, delegateQueue: .main) do { // 绑定随机端口,无需固定 try udpSocket.bind(toPort: 0) try udpSocket.enableBroadcast(true) try udpSocket.beginReceiving() // 生成NBNS通用查询包(获取网段内所有响应设备) guard let queryData = generateNBNSQueryData() else { print("生成NBNS查询包失败") return } // 发送到广播地址的137端口 udpSocket.send(queryData, toHost: broadcastIP, port: nbnsPort, withTimeout: -1, tag: 0) print("已发送NBNS广播查询") } catch { print("UDP初始化失败: \(error)") } } // 生成标准NBNS查询数据包 private func generateNBNSQueryData() -> Data? { var data = Data() // 1. 随机事务ID(2字节) let transactionID = UInt16.random(in: 0...65535) data.append(Data(bytes: &transactionID, count: 2)) // 2. 查询标志(2字节,0x0000表示普通查询) let flags: UInt16 = 0x0000 data.append(Data(bytes: &flags, count: 2)) // 3. 问题计数(2字节,设为1) let questionCount: UInt16 = 0x0001 data.append(Data(bytes: &questionCount, count: 2)) // 4. 回答、授权、附加计数各2字节,均为0 let zero: UInt16 = 0x0000 data.append(Data(bytes: &zero, count: 2)) data.append(Data(bytes: &zero, count: 2)) data.append(Data(bytes: &zero, count: 2)) // 5. 查询名称:通用查询"*",按NBNS格式编码 let queryName = "*" for char in queryName.utf8 { data.append(0x01) // 单个字符长度 data.append(char) } data.append(0x00) // 名称结束标志 // 6. 查询类型:0x0020(NetBIOS主机名) let queryType: UInt16 = 0x0020 data.append(Data(bytes: &queryType, count: 2)) // 7. 查询类:0x0001(IN类) let queryClass: UInt16 = 0x0001 data.append(Data(bytes: &queryClass, count: 2)) return data } // 处理收到的NBNS响应 func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) { guard let host = GCDAsyncUdpSocket.host(fromAddress: address) else { return } print("收到来自 \(host) 的NBNS响应") // 解析响应中的主机名 if let hostName = parseNBNSResponse(data: data) { print("解析结果:IP \(host) 对应主机名 \(hostName)") } } // 解析NBNS响应数据包 private func parseNBNSResponse(data: Data) -> String? { // 跳过前12字节的固定头字段 guard data.count > 12 else { return nil } var offset = 12 // 跳过查询名称部分(直到遇到结束标志0x00) while offset < data.count { let byte = data[offset] if byte == 0x00 { offset += 1 break } offset += Int(byte) + 1 } // 跳过查询类型和类字段(各2字节) offset += 4 // 检查回答部分数据长度 guard offset + 10 <= data.count else { return nil } // 提取NetBIOS名称 let nameLength = Int(data[offset]) offset += 1 guard offset + nameLength <= data.count else { return nil } let nameData = data.subdata(in: offset..<offset+nameLength) // 转换为字符串并过滤空字符 return String(data: nameData, encoding: .ascii)?.trimmingCharacters(in: .controlCharacters) } } // 使用示例 let resolver = NBNSResolver() resolver.startNBNSQuery()
额外注意事项
- 需在APP的
Info.plist中添加NSLocalNetworkUsageDescription,申请本地网络访问权限 - 部分设备可能关闭了NetBIOS服务,不会响应NBNS查询
- 若要定向查询某台设备,将
broadcastIP替换为目标设备的IP即可
内容的提问来源于stack exchange,提问作者Katz
相关产品推荐
相关产品推荐

