Получение информации о полученном UDP-сообщении широковещательно или одноадресно - PullRequest
0 голосов
/ 29 июня 2018

Добрый день всем!

Я разрабатываю модуль Python, который позволяет обнаруживать и настраивать клиентские устройства в локальной IP-сети с использованием сокета UDP. Я строю свое решение на основе существующего протокола, чтобы его можно было использовать с уже существующим программным обеспечением, но которое позволяет использовать только один порт. Таким образом, я не могу решить проблему, используя разные порты для разных типов сообщений. Но безопасность протокола в значительной степени зависит от того, какой запрос должен быть принят или отклонен, в зависимости от того, был ли запрос адресован широковещательной передаче или непосредственно устройству.

Моя основная проблема: я не могу найти способ (в Python) отличать широковещательные сообщения от одноадресных (полученных на тот же порт / сокет).

Кроме того, я ищу решения для Linux и Windows. Кроме того, на некоторых (встроенных, OpenWrt) устройствах, которые должны запускать этот сервис, установка модулей, написанных на cPython, затруднена, поэтому я настоятельно предпочитаю использовать только встроенные ресурсы на стороне Linux. Я также застрял в использовании Python, но если это не помогло, я могу использовать и внешние инструменты.

Буду рад любой помощи или совету. (Это мой первый пост на stackoverflow)

Ответы [ 2 ]

0 голосов
/ 10 июля 2018

Итак, мне наконец удалось решить мою проблему. Ответ Гамильтона был очень уместным и приблизил решение, но, как он упомянул, оно может не работать (и не будет) в Windows. Решение Linux почти работает на обоих, но есть несколько очень раздражающих различий с сокетами Wondows:

  • Вы не можете привязать сокет к широковещательному адресу. (ни один из n.n.n.255 или 255.255.255.255 не работает)
  • Вы можете привязать сокет к anycast (0.0.0.0) или любому действительному локальному адресу, и он будет получать только одноадресные сообщения.
  • Если вы установите socketopt SO_BROADCAST перед привязкой, он также будет принимать одноадресные и широковещательные сообщения (и вы не заметите разницу).
  • Если вы хотите привязать несколько сокетов к одному и тому же порту, вы должны установить socketopt SO_REUSEADDR для всех сокетов.
  • Если вы создаете одноадресную рассылку и сокет «Bothcast» и привязываете к одному и тому же порту, вы в конечном итоге получаете оба сообщения одноадресной рассылки.
  • Если вы используете select (питается этими двумя сокетами, как в примере с Linux), и они получают одно и то же одноадресное сообщение, select возвращает только один в первом раунде. (это личный эксперимент с Windows 10)

Я закончил с этим обходным путем:

import platform
from socket import gethostbyname, gethostname, socket, AF_INET, SOCK_DGRAM, SOL_SOCKET, SO_BROADCAST, SO_REUSEADDR
from select import select

PORT = 9999
myPublicIP = gethostbyname(gethostname())
platform = platform.system()

bsock = socket(AF_INET, SOCK_DGRAM)
usock = socket(AF_INET, SOCK_DGRAM)

if platform == 'Windows':
    usock.setsockopt(SOL_SOCKET, SO_BROADCAST, 0)  # This isn't necessary
    usock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    bsock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
    bsock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    usock.bind(('', PORT))
    bsock.bind(('', PORT))
    # empty string means anycast
elif platform == 'Linux':
    # on Linux SO_BROADCAST only affects the sending
    usock.bind((myPublicIP, PORT))
    bsock.bind(('255.255.255.255', PORT))

while True:
    # This is the tricky part
    select([bsock, usock], [], [])
    # if a bcast msg received the first select gives back only the usock instead of both
    ready = select([bsock, usock], [], [])[0]
    # the second select will give back both at the same time
    if bsock in ready and usock in ready:
        bmsg = bsock.recvfrom(1024)
        umsg = usock.recvfrom(1024)
        in_msg, in_addr = bmsg
        print("Got broadcast msg", in_msg, "from", in_addr)
        if bmsg != umsg:
            in_msg, in_addr = umsg
            print("Got unicast msg", in_msg, "from", in_addr)
    else:
        if bsock in ready:
            in_msg, in_addr = bsock.recvfrom(1024)
            print("Got broadcast msg", in_msg, "from", in_addr)
        if usock in ready:
            in_msg, in_addr = usock.recvfrom(1024)
            print("Got unicast msg", in_msg, "from", in_addr)
0 голосов
/ 29 июня 2018

В linux есть опция сокета (SO_PKTINFO), которая идеально подходит для этого - она ​​точно сообщит, кому была отправлена ​​датаграмма. Страница руководства для него говорит, что это только для Linux. Однако страница windows WSARecvMsg , похоже, указывает, что она поддерживается и там.

См. Также этот ответ: Как различить UDP-трансляции и одноадресные передачи?

Если этого недостаточно, самый простой способ сделать это - создать два сокета на принимающей стороне. Оба могут использовать один и тот же порт, но будут связаны с разными адресами. Например, скажем, ваш локальный IP-адрес - 192.168.0.100. Затем вы создаете два сокета: один связан с локальным IP-адресом, а другой - с широковещательным адресом.

from socket import socket, AF_INET, SOCK_DGRAM
from select import select

usock = socket(AF_INET, SOCK_DGRAM)
usock.bind(('192.168.0.100', 9999))

bsock = socket(AF_INET, SOCK_DGRAM)
bsock.bind(('255.255.255.255', 9999))

while True:
    rrdy, wrdy, xrdy = select([usock, bsock], [], [])
    if usock in rrdy:
        in_msg, in_addr = usock.recvfrom(1024)
        print("Got unicast msg", in_msg, "from", in_addr)
    if usock in rrdy:
        in_msg, in_addr = bsock.recvfrom(1024)
        print("Got broadcast msg", in_msg, "from", in_addr)

Это определенно работает на Linux. Я не знаю наверняка, что это будет работать на Windows, но кажется разумным ожидать, что это будет.

Я использую select здесь для получения сообщений из любого сокета, но вы могли бы легко выделить отдельный поток для одного из портов, если бы им было легче управлять.

...