From fdded43ab9b1818d6e32193ddb454dabf496ac8c Mon Sep 17 00:00:00 2001 From: Michael Sauer Date: Mon, 21 Sep 2026 23:06:01 +0200 Subject: [PATCH] fix(sacn): Multicast kam nie an - htonl() zerriss die Gruppenadresse Die Lampe trat seit jeher der falschen Gruppe bei. Aus Universe 1 wurde 239.255.239.255 statt 239.255.0.1, und damit lauschte sie auf einer Adresse, auf die niemand sendet: Multicast kam nie an, Unicast lief, und der Beitritt meldete trotzdem Erfolg - er gelang ja, nur eben fuer die falsche Gruppe. Ursache ist ein nicht geklammertes Makro im SDK: #define htonl(n) (((n & 0xff) << 24) | ((n & 0xff00) << 8) | ...) Stand die Adressrechnung direkt im Aufruf, traf die Maske nach der Textersetzung nur den letzten Term, weil & staerker bindet als |. Nachgerechnet ergibt das fuer Universe 1 exakt die 0xefffefff, die am EVK auf der seriellen Konsole stand. Die Rechnung liegt jetzt in nl_e131_group_addr() - im reinen, host-testbaren Teil, mit Testfaellen fuer beide Byte-Haelften. Der Aufrufer reicht eine Variable an htonl(), keinen Ausdruck mehr. Gefunden am EVK ueber die serielle Konsole, nachdem vier Netz-Konfigurationen und fuenf andere Theorien nichts ergeben hatten. --- src/net/nl_e131.c | 5 +++++ src/net/nl_e131.h | 6 ++++++ src/net/nl_sacn.c | 17 +++++++++++++++-- test/test_e131.c | 14 ++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/net/nl_e131.c b/src/net/nl_e131.c index bae8ed9..bcadf35 100644 --- a/src/net/nl_e131.c +++ b/src/net/nl_e131.c @@ -69,3 +69,8 @@ bool nl_e131_parse(const uint8_t *buf, size_t len, nl_e131_packet_t *out) out->slots = buf + NL_E131_MIN_PACKET; return true; } + +uint32_t nl_e131_group_addr(uint16_t universe) +{ + return 0xefff0000u | ((uint32_t)(universe >> 8) << 8) | (uint32_t)(universe & 0xffu); +} diff --git a/src/net/nl_e131.h b/src/net/nl_e131.h index 9a403ce..701cf3d 100644 --- a/src/net/nl_e131.h +++ b/src/net/nl_e131.h @@ -23,4 +23,10 @@ typedef struct { * `out` wird nur bei true beschrieben. */ bool nl_e131_parse(const uint8_t *buf, size_t len, nl_e131_packet_t *out); +/* Die Multicast-Gruppe eines Universe nach E1.31 Anhang A: 239.255.., als Zahl in + * HOST-Byte-Reihenfolge. Steht hier und nicht im Empfangs-Task, damit sie im Host-Test + * nachgerechnet werden kann - der Wert war schon einmal falsch, und man sieht es einer + * Lampe nicht an: sie tritt dann sauber einer Gruppe bei, auf die niemand sendet. */ +uint32_t nl_e131_group_addr(uint16_t universe); + #endif diff --git a/src/net/nl_sacn.c b/src/net/nl_sacn.c index 0a756d0..ea7f862 100644 --- a/src/net/nl_sacn.c +++ b/src/net/nl_sacn.c @@ -57,6 +57,7 @@ static int open_socket(uint16_t universe, char ip_seen[16]) struct ip_mreq mreq; struct timeval tv; nl_wifi_status_t w; + uint32_t group; int on = 1; int fd = socket(AF_INET, SOCK_DGRAM, 0); @@ -90,8 +91,20 @@ static int open_socket(uint16_t universe, char ip_seen[16]) * nicht beitritt oder das WLAN die Gruppe nicht weiterreicht. Genau diese Frage * beantwortet das Feld. */ memset(&mreq, 0, sizeof mreq); - mreq.imr_multiaddr.s_addr = - htonl(0xefff0000u | ((uint32_t)(universe >> 8) << 8) | (universe & 0xffu)); + /* Die Gruppenadresse wird ZUERST in eine Variable gerechnet und erst dann umgedreht. + * Das ist kein Schoenheitsfehler: htonl() ist in diesem SDK ein Makro, das sein + * Argument NICHT klammert (sdk/Include/Net/wm_sockets2.0.3.h): + * + * #define htonl(n) (((n & 0xff) << 24) | ((n & 0xff00) << 8) | ...) + * + * Ein Ausdruck mit | wird dabei zerrissen, weil & staerker bindet als | - die Maske + * trifft nur den letzten Term. Frueher stand der ganze Ausdruck direkt im Aufruf, und + * fuer Universe 1 kam 239.255.239.255 statt 239.255.0.1 heraus. Die Lampe trat also + * sauber einer Gruppe bei, auf die niemand sendete: Multicast kam nie an, waehrend + * Unicast lief, und der Beitritt meldete trotzdem Erfolg. Gefunden am 2026-09-21 am + * EVK ueber die serielle Konsole. */ + group = nl_e131_group_addr(universe); + mreq.imr_multiaddr.s_addr = htonl(group); mreq.imr_interface.s_addr = ip_seen[0] ? inet_addr(ip_seen) : htonl(INADDR_ANY); s_joined = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof mreq) == 0; printf("sacn: universe %u, multicast join %s\r\n", (unsigned)universe, diff --git a/test/test_e131.c b/test/test_e131.c index b03158f..6df0c9c 100644 --- a/test/test_e131.c +++ b/test/test_e131.c @@ -192,9 +192,23 @@ static void test_universe_too_high_rejected(void) TEST_ASSERT_FALSE(nl_e131_parse(pkt, pkt_len, &p)); } +static void test_group_address_per_annex_a(void) +{ + /* 239.255... Der Fehler, den dieser Test festhaelt: stand die Rechnung direkt + * im htonl()-Aufruf, zerriss das nicht geklammerte SDK-Makro sie, und aus Universe 1 + * wurde 239.255.239.255 - eine Gruppe, auf die niemand sendet. */ + TEST_ASSERT_EQUAL_HEX32(0xefff0001u, nl_e131_group_addr(1)); + TEST_ASSERT_EQUAL_HEX32(0xefff000cu, nl_e131_group_addr(12)); + TEST_ASSERT_EQUAL_HEX32(0xefff012cu, nl_e131_group_addr(300)); + TEST_ASSERT_EQUAL_HEX32(0xefff00ffu, nl_e131_group_addr(255)); + TEST_ASSERT_EQUAL_HEX32(0xefff0100u, nl_e131_group_addr(256)); + TEST_ASSERT_EQUAL_HEX32(0xeffff9ffu, nl_e131_group_addr(63999)); +} + int main(void) { UNITY_BEGIN(); + RUN_TEST(test_group_address_per_annex_a); RUN_TEST(test_valid_packet); RUN_TEST(test_full_universe); RUN_TEST(test_empty_slot_set);