1

I'm attempting a UDP broadcast from an embedded device. Platform is an ESP32; IP stack is from LwIP. I've removed all the error checking, but my calls to socket() and setsockopt() are successful. I get an errno 5 (I/O error) on my sendto() call. Can someone see what I'm doing wrong?

static int iobAnnounce;
const char broadcastEnable = 1;
struct sockaddr_in servaddrAnnounce;
char udp_data[22];
int err;

iobAnnounce = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    
setsockopt(iobAnnounce, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable));

memset(&servaddrAnnounce, 0, sizeof(servaddrAnnounce));
servaddrAnnounce.sin_family = AF_INET;
servaddrAnnounce.sin_port = htons(30303);
servaddrAnnounce.sin_addr.s_addr = htonl(INADDR_BROADCAST);

sendto(iobAnnounce,
       udp_data,
       22,
       0,
       (const struct sockaddr *) &servaddrAnnounce,
       sizeof(servaddrAnnounce));

Thanks for any assistance.

EDIT

Per @Barmar's question, I modified my code as follows:

err = tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); // ESP IDF call.
uint32_t subnetBroadcast = ip_info.ip.addr | ~(ip_info.netmask.addr);
servaddrAnnounce.sin_addr.s_addr = (subnetBroadcast);

Unfortunately, this didn't change the behavior. I also tried performing a htonl() on the address returned by tcpip_adapter_get_ip_info(); no difference. The output from the (removed) telltales was:

I (5354) IOB: iob_task(): tcpip_adapter_get_ip_info() returned ip d456a8c0; netmask 00ffffff; gateway 0156a8c0.
I (5364) IOB: iob_task(): subnetBroadcast is ff56a8c0.
I (5374) IOB: iob_task(): servaddrAnnounce.sin_addr.s_addr is c0a856ff.
mzimmers
  • 857
  • 7
  • 17
  • Does it work if you use the subnet broadcast address instead of the general broadcast address? – Barmar Jun 06 '22 at 20:42
  • In the error messages(s), the reported `netmask` looks suspicious. It is `00ffffff`. I would expect something like: `ffffff00` Is this just a printing error (e.g. need to byte swap to fix endianess) or is it truly that the MSB is 0? To me (my naive eye), some of the addresses appear endian reversed. In particular, `subnetBroadcast/ff56a8c0` and `servaddrAnnounce.sin_addr.s_addr/c0a856ff` appear inconsistent/reversed. Who is printing the error message(s)? Does `tcpip_*` do something unusual like expect native/little-endian and do the conversion internally to network/big-endian? – Craig Estey Jun 06 '22 at 22:27
  • Possibly related/dupe: [UDP client doesn't broadcast message on esp32](https://stackoverflow.com/questions/55831612/) – Remy Lebeau Jun 06 '22 at 22:55
  • @RemyLebeau interesting, but I'm not running BT. – mzimmers Jun 06 '22 at 23:03
  • @CraigEstey I thought I replied already -- sorry. The printouts are mine. I don't do any conversion in the first two, but I do in the third. From a byte ordering perspective, the data looks OK to me. – mzimmers Jun 06 '22 at 23:52
  • Using Ted's suggestion (re. `int broadcastEnable`) was mandatory for your program under my linux system (and no error on `sendto`). Have you tried running the unit tests that come with lwip? Based on all the replies here, I was thinking (even before seeing Remy's comment) that it was something about lwip itself and ESP32. That is, does it communicate with the system's hardware/OS? You may want to try a simple TCP connect to another system (e.g. your development system) to verify basic operation vs. something about the way you're doing broadcast. Which ESP32 board/configuration? – Craig Estey Jun 07 '22 at 01:46

3 Answers3

3

From setsockopt:

"Most socket-level options utilize an int argument for optval. For setsockopt(), the argument should be nonzero to enable a boolean option, or zero if the option is to be disabled."

... so, changing broadcastEnable from being a char to being an int should do it:

const int broadcastEnable = 1;
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
2

In addition to what @TedLyngmo said in his answer, there is another problem with your code. You are not calculating the subnetBroadcast correctly. You need to AND the ip_info.ip.addr with the ip_info.netmask.addr before you OR the inverse of the ip_info.netmask.addr, eg:

// uint32_t subnetBroadcast = ip_info.ip.addr | ~(ip_info.netmask.addr);
uint32_t subnetBroadcast = (ip_info.ip.addr & ip_info.netmask.addr) | ~(ip_info.netmask.addr);
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • thanks for pointing that out. I'm still getting the error. I even changed the address to that of my PC (so it's not really a broadcast anymore) and I *still* get the error. I think something more fundamental is at play here. – mzimmers Jun 06 '22 at 23:28
  • Then it is most likely a fault with LwIP itself, not your code. The ESP32 LwIP documentation states that `SO_BROADCAST` and `sendto()` are fully supported. – Remy Lebeau Jun 06 '22 at 23:52
2

Well, I found the problem. From the lwip_sendto() function:

  LWIP_ERROR("lwip_sendto: invalid address", (((to == NULL) && (tolen == 0)) ||
         (IS_SOCK_ADDR_LEN_VALID(tolen) &&
          ((to != NULL) && (IS_SOCK_ADDR_TYPE_VALID(to) && IS_SOCK_ADDR_ALIGNED(to))))),
         sock_set_errno(sock, err_to_errno(ERR_ARG)); done_socket(sock); return -1;);

It's the IS_SOCK_ADDR_ALIGNED() test that failed -- evidently, the sockaddr struct needs to be aligned on a 4-byte boundary. And, evidently, the error message is somehow suppressed, so I never saw the error. Geez...

Thanks to everyone for looking.

mzimmers
  • 857
  • 7
  • 17
  • Alignment errors are always troublesome and lurking when hidden behind casts. Great that you found the cause and +1 for posting the solution here. – Ted Lyngmo Jun 07 '22 at 19:00