【问题标题】:Unable to receive known reply when broadcasting UDP datagrams over LAN using Sockets or UdpClient使用 Sockets 或 UdpClient 通过 LAN 广播 UDP 数据报时无法接收已知回复
【发布时间】:2020-03-11 01:30:18
【问题描述】:

我已经搜索了 2 天,发现很多很多问题/答案似乎是同一个问题,虽然有一些差异,但似乎没有一个真正提供解决方案。

我正在实现一个库,用于在没有 OEM 控制器的情况下直接控制 DMX 系统(ColorKinetics 设备)。这涉及通过驱动照明设备的路由器与连接到我家 LAN 的支持以太网的电源 (PDS) 进行通信。 PDS 在特定端口 (6038) 上运行,并响应通过网络广播的格式正确的数据报。

我可以成功广播一个简单的 DMX 消息(标题 + DMX 数据),它被 PDS 拾取并应用于连接的照明设备,因此单向通信不是问题。

我的问题是我现在正在尝试实现设备发现功能来检测 LAN 上的 PDS 和连接的灯,并且我无法接收(绝对)从 PDS 发回的数据报.我可以成功传输指示设备回复的数据报,并且可以在 WireShark 中看到回复,但我的应用程序没有检测到回复。

我还尝试在另一台机器上运行一个简单的侦听器应用程序,它可以检测到初始广播,但也听不到返回的数据报,但是我认为这不起作用,因为返回的数据包是发给原始发送者 IP地址。

我最初尝试通过UdpClient 实现,然后通过Sockets 实现,无论我指定什么选项和参数,两者都会产生相同的结果。

这是我目前用于测试功能的非常简单的代码,目前使用Sockets

byte[] datagram = new CkPacket_DiscoverPDSRequestHeader().ToPacket();
Socket sender = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);    
IPEndPoint ep = new IPEndPoint(IPAddress.Parse("192.168.1.149"), 6039);

public Start()
{    
    // Start listener
    new Thread(() =>
    {
        Receive();
    }).Start();

    sender.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
    sender.EnableBroadcast = true;
    // Bind the sender to known local IP and port 6039
    sender.Bind(ep);
}

public void Send()
{            
    // Broadcast the datagram to port 6038
    sender.SendTo(datagram, new IPEndPoint(IPAddress.Broadcast, 6038));
}

public void Receive()
{
    Socket receiver = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
    receiver.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
    receiver.EnableBroadcast = true;

    // Bind the receiver to known local IP and port 6039 (same as sender)
    IPEndPoint EndPt = new IPEndPoint(IPAddress.Parse("192.168.1.149"),6039);
    receiver.Bind(EndPt);

    // Listen
    while (true)
    {
        byte[] receivedData = new byte[256];

        // Get the data
        int rec = receiver.Receive(receivedData);

        // Write to console the number of bytes received
        Console.WriteLine($"Received {rec} bytes");
    }    
}

发送者和接收者绑定到一个IPEndPoint,本地IP和端口为6039。我这样做是因为我可以看到每次我初始化一个新的UdpClient时,系统会动态分配一个传出端口,这PDS 会将数据发送回。这样做,我可以说侦听器肯定在侦听应该接收 PDS 响应 (6039) 的端口。我相信,由于我将选项 ReuseAddress 设置为 true,这应该不是问题(不会引发异常)。

Start() 创建一个新线程来包含侦听器,并初始化发送客户端上的选项。

Send()成功广播PDS在6038端口接收到的16字节数据报,并在6039端口产生回复(见WireShark)

Receive() 没有收到数据报。如果我将监听器绑定到 6038 端口,它将接收原始的 16 字节数据报广播。

这是 WireShark 的数据:

Wireshark

正如许多答案所建议的那样,我已经研究过使用像 SharpPCap 这样的库,但在最新版本中似乎存在一些我不够聪明无法规避的兼容性问题,这会阻止基本示例正常运行在我的系统上。似乎这种基本功能不需要那种类型的外部依赖。我还看到了许多其他类似问题的问题/答案,但是通过为SocketUdpClient 设置这个或那个参数来解决它,其中我尝试了所有组合都无济于事。

我还启用了通过windows防火墙的访问权限,允许端口使用,甚至完全禁用了防火墙,但没有成功。我不认为问题出在我的路由器上,因为消息正在发送到 Wireshark。

更新 1

根据建议,我相信我将侦听器 Socket 置于混杂模式如下:

    Socket receiver = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
    receiver.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, true);
    receiver.EnableBroadcast = true;

    IPEndPoint EndPt = new IPEndPoint(IPAddress.Parse("192.168.1.149"), 0);

    receiver.Bind(EndPt);
    receiver.IOControl(IOControlCode.ReceiveAll, new byte[] { 1, 0, 0, 0 }, null);

这导致监听器接收到各种网络流量,包括出站请求,但仍然没有收到回复。

更新 2

正如 Viet 所建议的,请求数据报中存在某种寻址问题,其格式如下:

    public class CkPacket_DiscoverPDSRequest : BytePacket
    {
        public uint magic = 0x0401dc4a;
        public ushort version = 0x0100;
        public ushort type = 0x0100;
        public uint sequence = 0x00000000;
        public uint command = 0xffffffff;
    }

如果我将command 字段更改为我的广播地址192.168.1.149' or192.168.255.255`,我的监听器开始检测返回的数据包。我承认我不知道这个字段应该代表什么,我最初的猜测是只输入一个广播地址,因为数据报的目的是发现网络上的所有设备。显然不是这样,尽管我仍然不确定它的确切意义。

不管怎样,谢谢你的帮助,这就是进步。

【问题讨论】:

  • 有几件事:首先,从不使用ReuseAddress。如果您正在使用它,那么您做错了,特别是如果您是套接字编程的新手。其次,在您发布的代码中,两个套接字都绑定到端口 6039,但您发送到端口 6038。似乎仅此一项就可以解释您无法接收任何数据报。不幸的是,您的问题不够清楚,无法回答。它没有提供好的minimal reproducible example,并且不清楚您是需要简单演示程序的帮助(即minimal reproducible example)还是需要实际网络设备的帮助。后者不太可能导致答案。
  • 感谢您的回复;你能澄清为什么ReuseAddress 不是一个好主意吗?在我所见过的类似程序的每个示例中都有。
  • 至于绑定socket 6039,我想我可能还不清楚;我正在与之通信的 PDS 硬连线以在端口 6038 上进行通信,因此它仍在接收数据包,并且它回复用于发送的任何端口(在本例中为 6039)。将我这边的两个 Socket 绑定到 6039 可确保侦听器正在侦听预期响应的正确端口,因为否则该端口将动态分配给发送 Socket,我将不得不找出另一种方法来侦听该端口在运行时(至少是我的想法)。
  • 我不确定我的示例中缺少什么,它本质上是我目前正在运行的确切程序,没有依赖项列表,它的行为与我描述的完全一样。它通过端口 6039 和目标端口 6038 发送数据报,PDS 接收它,发送一个目标为 6039 的回复(在 Wireshark 中查看),侦听器在端口 6039 上没有收到它。就像我说的,这是本来是一个非常简单的测试程序,可以帮助我弄清楚这种类型的编程需要如何工作,但实际计划的实现并不复杂。

标签: c# sockets udp udpclient


【解决方案1】:

所以实际上我的问题在于传出数据报的格式。 command 字段必须是本地子网 192.168.xxx.xxx 上的地址,而不是 255.255.255.255... 无论出于何种原因,这都会导致数据包在到达我的应用程序之前在某处被过滤,尽管 WireShark 仍然可以看到它.这在这类工作中可能是常识,但对网络编程以及此接口的细节相对无知,这不是我考虑过的。

进行更改可使简单的 UdpClient 发送/接收功能完美运行。

非常感谢 Viet Hoang 帮我找到这个!

【讨论】:

    【解决方案2】:

    正如您已经注意到的,您无需绑定即可发送广播,但它使用随机源端口。

    如果您将代码调整为不绑定发送者,您的侦听器应该再次按预期运行:

    Socket sender = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);    
    sender.EnableBroadcast = true;
    Thread read_thread;
    
    public Start()
    {    
        // Start listener
        read_thread = new Thread(Receive);
        read_thread.Start();
    }
    

    您遇到的问题是操作系统内核仅将数据包传送到一个套接字绑定器(先到先得)。

    如果您想要真正的并行读取访问,则需要查看嗅探示例,例如:https://stackoverflow.com/a/12437794/8408335

    由于您只想从相同的 ip/端口获取广播,您只需要先让接收器绑定即可。 如果你在启动接收线程之后,在绑定发送者之前添加一个短暂的睡眠,你将能够看到预期的结果。

    public Start()
    {    
        // Start listener
        new Thread(() =>
        {
            Receive();
        }).Start();
    
        Thread.Sleep(100);
    
        sender.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        sender.EnableBroadcast = true;
        // Bind the sender to known local IP and port 6039
        sender.Bind(ep);
    }
    

    额外说明:您可以使用 netcat 从 linux 机器快速测试您的 udp 套接字:

    # echo "hello" | nc -q -1 -u 192.168.1.149 6039 -
    

    - 编辑-

    问题第 2 部分

    “255.255.255.255”的源地址无效。 使用两个相同的数据包更改源 IP 进行了快速测试:

    https://i.stack.imgur.com/BvWIa.jpg

    只有具有有效源 IP 的那个被打印到控制台。

    Received 26 bytes
    Received 26 bytes
    

    【讨论】:

    • 感谢您的回复。您对绑定的两个建议似乎都没有改变任何东西——关于先到先得交付的评论是有道理的,但是添加暂停并没有达到预期的效果。我查看了链接中的示例,并实现了简单的原始套接字功能,并且监听器开始接收各种其他网络流量,包括正在发送的传出请求,但仍然没有检测到回复包。
    • 我还在我的另一台机器上实现了原始套接字,它也基本上检测除了 PDS 回复数据包之外的每个网络数据包,这让我认为该数据包特别有什么东西以及它是如何解决了它在进入我的程序之前被忽略或以其他方式过滤...这是 WireShark 能够规避的事情。
    • @kenaschmidt,在发布之前,该代码已在 C# 中使用 netcat 进行了测试。首先,绑定顺序确实很重要,所以请记住这一点。其次,考虑到附加信息,返回数据包很可能格式错误并被过滤。如果可以的话,我强烈推荐使用 netcat 创建一个健康的测试包来使用。在原始模式下,您可以看到 NIC 环形缓冲区上的所有内容。可能会发生硬件级过滤。 WireShare 将接口置于“混杂模式”,可以拾取在硬件级别过滤的其他数据包。
    • 谢谢 - 我在我的帖子中添加了一个编辑,我认为这是混杂模式下的听众。它让我可以看到各种其他网络流量,但是这个特定的数据包仍然没有通过。
    • @kenaschmidt,所以检查数据包摘要图像,数据包 - 有效负载 = 42 字节,这是正常的。然而,源地址在技术上是无效的。继续并将其设置为 PDS 的 IP,并将其作为潜在的麻烦制造者也划掉。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-05
    • 1970-01-01
    • 2019-05-12
    相关资源
    最近更新 更多