Hundemeier / sacn

A simple ANSI E1.31 (aka sACN) module for python.
MIT License
48 stars 21 forks source link

Interface bind on Linux doesn't really work for listening #47

Open andrewyager opened 5 months ago

andrewyager commented 5 months ago

As the notes indicate, the interface bind on Linux doesn't work, particularly when we are dealing with multicast traffic. There are a range of reasons for this, but the socket implementation doesn't really lend itself to being used in this way.

The fix for this is to do something like:

import socket

...

interface_to_listen_on = b"eth0"
receiver._handler.socket._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, interface_to_listen_on)

It would be nice to simply extend sACNreceiver to allow this to be set without requiring a bit of internal digging to find that interface name. This is obviously a Linux only implementation; and I'm not sure that it is quite the same to implement in Windows - I have specifically not tested this in this environment.

I have tested this in Python 3.9+ on a range of 5 series kernels without issue.

Hundemeier commented 5 months ago

Implementing a bind_interface parameter for sACNreceiver should not be any problem. However, did you encounter any issues on Linux when trying to receive multicast sACN packets? #42 suggests that it should now be possible by providing an IP (maybe even 0.0.0.0). Is this potentially related to #45 ?

andrewyager commented 5 months ago

I think this may in fact be true; let me keep testing!

andrewyager commented 4 months ago

Closing issue as this seems to have been a product of #45

andrewyager commented 3 months ago

Coming back to this issue and re-opening it as I've now had to properly triage what is causing it.

I've done a fresh install of Ubuntu 22.04 with Python 3.10.2 and 5.15.0-112-generic.

The host has two IP interfaces:

ens3: 192.168.1.211/24 ens4: 192.168.5.17/24

I have started sACNView on another host and told it to broadcast onto Universe 1 on the the ens4 network.

I confirmed that tcpDump can see the packets on the ens4 network.

The following does not receive any incoming packets on the ens4 interface as far as the code is concerned.

So having tested that, I checked out a local copy of the sacn package and started tinkering.

The following change does, however, make everything work:

diff --git a/sacn/receiving/receiver_socket_udp.py b/sacn/receiving/receiver_socket_udp.py
index ab1fad0..d3935c4 100644
--- a/sacn/receiving/receiver_socket_udp.py
+++ b/sacn/receiving/receiver_socket_udp.py
@@ -26,7 +26,7 @@ class ReceiverSocketUDP(ReceiverSocketBase):
             self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         except socket.error:  # Not all systems support multiple sockets on the same port and interface
             pass
-        self._socket.bind((self._bind_address, self._bind_port))
+        self._socket.bind(('', self._bind_port))
         self._logger.info(f'Bind receiver socket to IP: {self._bind_address} port: {self._bind_port}')

     def start(self):

After making this change, I checked the following:

The following receives packets (as expected):

The following does not receive packets (probably as expected?)

Changing the source to be on the other interface (facing 192.168.1.211) will cause the host to receive packets in the second scenario (because ens3 is the first interface on the host).

This also reflects the multicast membership joins.

Interestingly, in all cases, specifying the correct interface always resulted in the ip maddr show command showing that the group memberships were correct; but obviously something about the 'bind' operation meant that it would not pick up the packets when they came in on the second interface.