如何在C#中通过IP获取机器的NetBIOS名称?

12

如果我有一个机器的IP地址,如何在C#中以编程方式获取其NetBIOS名称? 我知道我可以通过命令行使用“nbtstat -A”获取它,但我正在寻找更好的解决方案。


有趣的问题。我猜你可以查询DNS将IP映射到主机名,这个主机名可能与NetBIOS名称相同,也可能不同。我不确定是否存在专门针对NetBIOS的API或库。 - Bruce
5个回答

4

2
我使用基于微软一次找到的示例代码。 当它获得在端口137上的UDP请求的结果时,它会从答案中切除名称。 它不会检查这些名称是否是有效的NetBIOS名称。 可以添加此功能以使其更安全。
public class NetBIOSHelper
{
    /// <summary>
    /// Get the NetBIOS machine name and domain / workgroup name by ip address using UPD datagram at port 137. Based on code I found at microsoft 
    /// </summary>
    /// <returns>True if getting an answer on port 137 with a result</returns>
    public static bool GetRemoteNetBiosName(IPAddress targetAddress, out string nbName, out string nbDomainOrWorkgroupName, int receiveTimeOut = 5000, int retries = 1)
    {
        // The following byte stream contains the necessary message
        // to request a NetBios name from a machine
        byte[] nameRequest = new byte[]{
        0x80, 0x94, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41,
        0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
        0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
        0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
        0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21,
        0x00, 0x01 };

        do
        {
            byte[] receiveBuffer = new byte[1024];
            Socket requestSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            requestSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, receiveTimeOut);

            nbName = null;
            nbDomainOrWorkgroupName = null;

            EndPoint remoteEndpoint = new IPEndPoint(targetAddress, 137);
            IPEndPoint originEndpoint = new IPEndPoint(IPAddress.Any, 0);
            requestSocket.Bind(originEndpoint);
            requestSocket.SendTo(nameRequest, remoteEndpoint);
            try
            {
                int receivedByteCount = requestSocket.ReceiveFrom(receiveBuffer, ref remoteEndpoint);

                // Is the answer long enough?
                if (receivedByteCount >= 90)
                {
                    Encoding enc = new ASCIIEncoding();
                    nbName = enc.GetString(receiveBuffer, 57, 15).Trim();
                    nbDomainOrWorkgroupName = enc.GetString(receiveBuffer, 75, 15).Trim();

                    // the names may be checked if they are really valid NetBIOS names, but I don't care ....

                    return true;
                    //<----------
                }
            }
            catch (SocketException)
            {
                // We get this Exception when the target is not reachable
            }

            retries--;

        } while (retries >= 0);

        return false;
        //< ----------
    }
}

1
我简直不敢相信,但这个脚本是唯一能够工作的。你能解释一下你是如何找到nameRequest的值的吗? - Tib Schott
@TibSchott 这些值来自我曾在微软找到的一个示例。当我搜索前四个字节“0x80,0x94,0x00,0x00”时,会得到几个结果,也许其中一个有解释。 - marsh-wiggle
@TibSchott 在这个帖子中使用了类似的代码,但也没有解释:https://social.msdn.microsoft.com/Forums/vstudio/en-US/6ccac955-b6c4-4173-8390-1ce0b2fb82c8/urgent-pls-how-to-get-computer-name-from-given-fqdn-and-it-username-password。我记得我曾经在RFC中寻找UDP消息的定义,并意外地发现了这个例子,并且现在已经使用它多年了。 - marsh-wiggle
在我的情况下,似乎对我没有起作用。 - DennisVM-D2i
@DennisVM-D2i 远程机器上需要启用网络发现。您检查过了吗? - marsh-wiggle
显示剩余4条评论

1

您可以使用winapi gethostbyaddr 并指定类型为 AF_NETBIOS


0

顺便提一下,在尝试将接收到的MAC地址映射到组织名称(/供应商)方面,至少作为指南,这可能会有所帮助:

            IDictionary<string, string> ouiToOrgName = null;

            using (var httpClnt = new System.Net.Http.HttpClient())
            {
                var ouiTxt =
                    await httpClnt.GetStringAsync(@"https://standards-oui.ieee.org/oui/oui.txt");

                var ouiTxtList =
                    ouiTxt
                        .Split('\n')
                        .Where(x => x.Contains(@"(hex)"))
                        .ToList();

                ouiToOrgName =
                    new SortedDictionary<string, string>();

                foreach (var line in ouiTxtList)
                {
                    var match = OuiTxtOuiOrgNameRe.Match(line);

                    if (match.Success &&
                        // Ignore duplicates
                        ! ouiToOrgName.ContainsKey(match.Groups[1].Value))
                    {
                        ouiToOrgName.Add(
                            match.Groups[1].Value,
                            match.Groups[2].Value);
                    }
                }
            }

            return ouiToOrgName;

...

        private static readonly Regex OuiTxtOuiOrgNameRe =
            new Regex(
                @"((?:[0-9A-F]{2}-){2}[0-9A-F]{2})(?: *)(?:\(hex\))(?:\t*)(.*)",
                // RegexOptions.Multiline |
                RegexOptions.Compiled);


0

我稍微修改了上面介绍的类(NetBIOSHelper)。 它在Windows 7中错误地给出了工作组,但在Windows 10中是正确的。 因为win7的nbtstat首先给出所有名称,然后是所有组,而win10则是名称、组、名称、组。至少在我的网络中是这样的。 此外,我添加了另一个布尔返回参数, 它指示给定的计算机是否是该组的主浏览器(MasterBrowser)。 现在,该函数简单地返回object[],[string computername,string workgroup,bool isMasterBrowser]。

public class NetBIOSHelper{

public static object[] GetRemoteNetBiosName(IPAddress targetAddress, int receiveTimeOut = 5000, int retries = 1){
    byte[] nameRequest = new byte[]{
    0x80, 0x94, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x20, 0x43, 0x4b, 0x41,
    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
    0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
    0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x21,
    0x00, 0x01 };

    string workgroup = "";
    string compname = "";
    bool isMasterBrowser = false;
    do
    {
        byte[] receiveBuffer = new byte[1024];
        Socket requestSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        requestSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, receiveTimeOut);


        EndPoint remoteEndpoint = new IPEndPoint(targetAddress, 137);
        IPEndPoint originEndpoint = new IPEndPoint(IPAddress.Any, 0);
        requestSocket.Bind(originEndpoint);
        requestSocket.SendTo(nameRequest, remoteEndpoint);
        try
        {
            int receivedByteCount = requestSocket.ReceiveFrom(receiveBuffer, ref remoteEndpoint);
            if (receivedByteCount >= 90)
            {
                Encoding enc = new ASCIIEncoding();

                  for(int i = 0; i < receiveBuffer[56]; i++){                            
                        var name = enc.GetString(receiveBuffer, 57+i*18, 15).Trim();
                        byte b1 = receiveBuffer[57 + i * 18 + 15];
                        byte b2 = receiveBuffer[57 + i * 18 + 16];
                        if ((b2 & (1 << 7)) != 0 && (b2 & (1 << 2)) != 0 && b1 == 0x00)
                            workgroup = name;
                        if ((b2 & (1 << 7)) == 0 && (b2 & (1 << 2)) != 0 && b1 == 0x00)
                            compname = name;
                        if ((b2 & (1 << 2)) != 0 && b1 == 0x1D)
                            isMasterBrowser = true;
                    }

                return new object[]{
                        compname,
                        workgroup,
                        isMasterBrowser
                    };
            }
        }
        catch (SocketException e)
        {

        }

        retries--;

    } while (retries > 0);

    return null;
}
}

附言:还将循环条件更改为 while (retries > 0),因为使用参数1时会调用2次

另外关于第16个字节(变量b1),我找到了它的值,以下是它们的含义:

  • 00 标准工作站服务
  • 03 信使服务(WinPopup)
  • 06 RAS服务器服务
  • 1B 域主浏览器服务(与主域控制器相关联)
  • 1D 主浏览器名称
  • 1F NetDDE服务
  • 20 文件服务器(包括打印机服务器)
  • 21 RAS客户端服务
  • BE 网络监视器代理
  • BF 网络监视器实用程序

对于组资源:

  • 00 标准工作站组
  • 1C 登录服务器
  • 1D 主浏览器名称
  • 1E 普通组名称(用于浏览器选举)
  • 20 互联网组名称(管理)
  • 01-MSBROWSE(有关其他主浏览器的警报)

您可以阅读更多关于第17个字节(变量b2)和第18个字节包含的内容这里, 在那里您需要将它们分割为位。 总的来说,您可以在RFC 1001RFC 1002以及这里中了解更多NetBIOS知识。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接