cesanta / mongoose

Embedded Web Server
https://mongoose.ws
Other
11.15k stars 2.73k forks source link

UDP Broadcast Receive and send response with RP2040 and W5500 #2164

Closed rickbassham closed 1 year ago

rickbassham commented 1 year ago

Modified the captive-dns-server example to test locally. Worked. Updated the example to use the RP2040 and W5500. Doesn't work.

Non-working log

11f0   3 mongoose.c:9849:rx_ip          UDP 192.168.1.25:45606 -> 192.168.1.255:32412 len 21
12f9   3 mongoose.c:9849:rx_ip          UDP 192.168.1.25:58257 -> 192.168.1.255:32414 len 21
1687   3 mongoose.c:9849:rx_ip          UDP 192.168.1.25:37930 -> 239.255.255.250:1900 len 101
1bb0   3 mongoose.c:9405:rx_arp         ARP: tell 192.168.1.1 we're 192.168.1.172
0000   84 47 09 16 f7 d6 02 42 e3 4e 63 22 08 06 00 01   .G.....B.Nc"....
0010   08 00 06 04 00 02 02 42 e3 4e 63 22 c0 a8 01 ac   .......B.Nc"....
0020   84 47 09 16 f7 d6 c0 a8 01 01                     .G........
2579   3 mongoose.c:9849:rx_ip          UDP 192.168.1.25:45606 -> 192.168.1.255:32412 len 21
2682   3 mongoose.c:9849:rx_ip          UDP 192.168.1.25:58257 -> 192.168.1.255:32414 len 21
2f73   3 mongoose.c:9849:rx_ip          UDP 192.168.1.119:49608 -> 192.168.1.255:32227 len 16
2f76   2 main.c:75:alpaca_discovery     Alpaca 192.168.1.119:49608 1
2f79   3 mongoose.c:10233:mg_send       mg_send udp
2f7a   3 mongoose.c:9316:tx_udp         UDP XX LEN 17 1540
2f7c   3 mongoose.c:9328:tx_udp         UDP LEN 17
0000   00 00 00 00 00 00 02 42 e3 4e 63 22 08 00 45 00   .......B.Nc"..E.
0010   00 2d 00 00 40 00 40 11 b6 4c c0 a8 01 ac c0 a8   .-..@.@..L......
0020   01 77 7d e3 c1 c8 00 19 10 fc 7b 22 41 6c 70 61   .w}.......{"Alpa
0030   63 61 50 6f 72 74 22 3a 38 30 7d                  caPort":80}
3039   3 mongoose.c:9849:rx_ip          UDP 192.168.1.214:5353 -> 224.0.0.251:5353 len 61
3424   3 mongoose.c:9849:rx_ip          UDP 192.168.1.214:5353 -> 224.0.0.251:5353 len 61
3902   3 mongoose.c:9849:rx_ip          UDP 192.168.1.25:45606 -> 192.168.1.255:32412 len 21
3a0c   3 mongoose.c:9849:rx_ip          UDP 192.168.1.25:58257 -> 192.168.1.255:32414 len 21

Working log:

./example                    
a575205 3 net.c:183:mg_listen           1 0x3 udp://0.0.0.0:32227
a576289 3 sock.c:274:read_conn          1 0x3 snd 0/0 rcv 0/8192 n=16 err=0
a576289 2 sock.c:104:iolog              
-- 1 0.0.0.0:32227 <- 192.168.1.119:64176 16
0000   61 6c 70 61 63 61 64 69 73 63 6f 76 65 72 79 31   alpacadiscovery1
a576289 3 sock.c:142:mg_send            1 0x3 0:16 17 err 0
a576289 2 sock.c:104:iolog              
-- 1 0.0.0.0:32227 -> 192.168.1.119:64176 17
0000   7b 22 41 6c 70 61 63 61 50 6f 72 74 22 3a 38 30   {"AlpacaPort":80
0010   7d                                                }               
a5c2594 3 sock.c:274:read_conn          1 0x3 snd 0/0 rcv 0/8192 n=16 err=0
a5c2594 2 sock.c:104:iolog              
-- 1 0.0.0.0:32227 <- 192.168.1.119:60950 16

Working Code:

#include "mongoose.h"

#define ALPACA_RESPONSE "{\"AlpacaPort\":80}"

static const char *s_listen_url = "udp://0.0.0.0:32227";

static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
  if (ev == MG_EV_OPEN) {
    c->is_hexdumping = 1;
  } else if (ev == MG_EV_READ) {
    {
      if (strncmp(c->recv.buf, "alpacadiscovery1", 16) == 0) {
        mg_send(c, ALPACA_RESPONSE, 17);
      }

      mg_iobuf_del(&c->recv, 0, c->recv.len);
    }
    (void) fn_data;
    (void) ev_data;
  }
}

int main(void) {
  struct mg_mgr mgr;

  mg_log_set(MG_LL_DEBUG);                  // Set log level
  mg_mgr_init(&mgr);                        // Initialise event manager
  mg_listen(&mgr, s_listen_url, fn, NULL);  // Start DNS server
  for (;;) mg_mgr_poll(&mgr, 1);            // Event loop
  mg_mgr_free(&mgr);

  return 0;
}

Non-working code:

#include <stdio.h>
#include <string.h>
#include "hardware/spi.h"
#include "pico/rand.h"
#include "pico/stdlib.h"
#include "pico/unique_id.h"

#include "mongoose.h"

#define ALPACA_RESPONSE "{\"AlpacaPort\":80}"

enum
{
  SPI_CS = 17,
  SPI_CLK = 18,
  SPI_TX = 19,
  SPI_RX = 16
}; // Pins

static void spi_begin(void *spi)
{
  gpio_put(SPI_CS, 0);
}

static void spi_end(void *spi)
{
  gpio_put(SPI_CS, 1);
}

static uint8_t spi_txn(void *spi, uint8_t byte)
{
  uint8_t result = 0;
  spi_write_read_blocking(spi0, &byte, &result, 1);
  return result;
}

void mg_random(void *buf, size_t len)
{
  for (size_t n = 0; n < len; n += sizeof(uint32_t))
  {
    uint32_t r = get_rand_32();
    memcpy((char *)buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r));
  }
}

static void eth_spi_init(void)
{
  // Init SPI pins
  spi_init(spi0, 500 * 1000);
  gpio_set_function(SPI_RX, GPIO_FUNC_SPI);  // MISO
  gpio_set_function(SPI_TX, GPIO_FUNC_SPI);  // MOSI
  gpio_set_function(SPI_CLK, GPIO_FUNC_SPI); // CLK
  gpio_init(SPI_CS);                         // CS
  gpio_set_dir(SPI_CS, GPIO_OUT);            // Set CS to output
  gpio_put(SPI_CS, 1);                       // And drive CS high (inactive)
}

// Helper macro for MAC generation
#define GENERATE_LOCALLY_ADMINISTERED_MAC(id) \
  {                                           \
    2, id[3], id[4], id[5], id[6], id[7]      \
  }

static void alpaca_discovery(struct mg_connection *c, int ev, void *ev_data, void *fn_data)
{
  if (ev == MG_EV_OPEN)
  {
    c->is_hexdumping = 1;
  }
  else if (ev == MG_EV_READ)
  {
    {
      if (strncmp(c->recv.buf, "alpacadiscovery1", 16) == 0)
      {
        MG_INFO(("Alpaca %M %u", mg_print_ip_port, &(c->rem), c->is_udp));

        mg_send(c, ALPACA_RESPONSE, 17);
      }

      mg_iobuf_del(&c->recv, 0, c->recv.len);
    }
    (void)fn_data;
    (void)ev_data;
  }
}

int main(void)
{
  stdio_init_all();

  eth_spi_init(); // Initialise SPI pins
  MG_INFO(("Starting ..."));

  pico_unique_board_id_t board_id;
  pico_get_unique_board_id(&board_id);
  uint8_t *id = board_id.id;

  struct mg_mgr mgr;       // Initialise
  mg_mgr_init(&mgr);       // Mongoose event manager
  mg_log_set(MG_LL_DEBUG); // Set log level

  // Initialise Mongoose network stack
  // Specify MAC address, and IP/mask/GW in network byte order for static
  // IP configuration. If IP/mask/GW are unset, DHCP is going to be used
  struct mg_tcpip_spi spi = {NULL, spi_begin, spi_end, spi_txn};
  struct mg_tcpip_if mif = {.mac = GENERATE_LOCALLY_ADMINISTERED_MAC(id),
                            .driver = &mg_tcpip_driver_w5500,
                            .driver_data = &spi};
  mg_tcpip_init(&mgr, &mif);

  MG_INFO(("MAC: %M. Waiting for IP...", mg_print_mac, mif.mac));
  while (mif.state != MG_TCPIP_STATE_READY)
  {
    mg_mgr_poll(&mgr, 0);
  }

  MG_INFO(("Initialising application..."));

  // mg_http_listen(&mgr, "http://0.0.0.0:80", alpaca_server, NULL);
  mg_listen(&mgr, "udp://0.0.0.0:32227", alpaca_discovery, NULL);

  MG_INFO(("Starting event loop"));
  for (;;)
  {
    mg_mgr_poll(&mgr, 1);
  }

  return 0;
}

Environment

scaprile commented 1 year ago

You are mixing unicast with broadcast and multicast. You are not just sending and receiving UDP, please change your subject. For examples on using multicast on an OS, see https://mongoose.ws/documentation/tutorials/udp-ssdp-search/ Then, you are mixing some OS networking stack with Mongoose built-in TCP/IP stack. Currently that stack does not support multicast (and probably neither broadcast). To do something like that, currently, you should choose a platform where lwIP works in socket mode (lwIP + FreeRTOS), in order to be able to replicate what you did in your OS; though this hasn't been tested either and YMMV. The current RP2040 + W5500 example does send and receive UDP (syncs time via SNTP, which runs on UDP), it just does not use broadcast nor multicast. Please do not post all the code, only relevant portions. What does not work ? Do you receive ? Can't you send ? Did you sniff your network ? Etc, Currently we are not planning to add support for broadcast nor multicast in Mongoose built-in stack, but if it is something simple we might consider adding it. What is your use case ?

rickbassham commented 1 year ago

I guess I was just trying to provide as much info as needed to show the issue. When using the OS stack, running on my Mac, my example code above works. When using the same relevant code, but using the Mongoose TCP/IP stack, I can receive the broadcast packet, but sending the response results in nothing going over the wire, verified with wireshark.

I'm likely just doing something wrong here, since the broadcast packet is received just fine, and just looking for some help getting this going. I like mongoose since it seems to be much more reliable and performant than a lot of other options out there, but if I can't get the discovery protocol working, I'll need to use something else.

My use case is implementing the ASCOM Alpaca discovery protocol, which sends a specific broadcast packet to the network and expects devices to respond if they implement the Alpaca protocol.

The broadcast is sent on port 32227 with alpacadiscovery1 as the packet contents. The expected return packet should contain a json string:

{"AlpacaPort":80}

Here is a link to a working example on an Arduino not using the Mongoose framework.

scaprile commented 1 year ago

Currently that stack does not support multicast (and probably neither broadcast).

The Pico SDK works with lwIP, and we have an example on using the Pico W with FreeRTOS. You can still run Mongoose over lwIP over FreeRTOS, though we haven't tested UDP broadcast nor multicast in that architecture

Currently we are not planning to add support for broadcast nor multicast in Mongoose built-in stack, but if it is something simple we might consider adding it.

I would've expected the broadcast packet to go out but with the wrong destination MAC, can you check for that ? Our ARP algorithm is pretty simple and is probably trying to resolve the broadcast address. I would bet it is sending to the default gateway, can you check that ? The fix would be to add a corner case and just use a broadcast MAC for broadcast destinations. These from the top of my head, I haven't worked on the stack for some time now.

rickbassham commented 1 year ago

It seems to resolve the remote IP and port correctly when I log those here:

        MG_INFO(("Alpaca %M %u", mg_print_ip_port, &(c->rem), c->is_udp));

        mg_send(c, ALPACA_RESPONSE, 17);
rickbassham commented 1 year ago

Also, I'm not trying to send a broadcast via mongoose, I'm trying to reply to a broadcast packet I received. Not sure if I made that clear above

rickbassham commented 1 year ago

One more thing I've tried is establishing a new "connection" to send the udp packet response:

      if (strncmp(c->recv.buf, "alpacadiscovery1", 16) == 0)
      {
        MG_INFO(("Alpaca %M %u", mg_print_ip_port, &(c->rem), c->is_udp));

        char url[100];
        memset(url, 0, sizeof(url));
        mg_snprintf(url, sizeof(url), "udp://%M", mg_print_ip_port, &(c->rem));

        mg_connect(c->mgr, url, alpaca_discover_send, NULL);

        // mg_send(c, ALPACA_RESPONSE, 17);
      }
static void alpaca_discover_send(struct mg_connection *c, int ev, void *ev_data, void *fn_data)
{
  MG_DEBUG(("Alpaca discover send %d", ev));

  if (ev == MG_EV_RESOLVE)
  {
    mg_send(c, ALPACA_RESPONSE, 17);
    c->is_draining = 1;
  }
}

But this doesn't have anything sent either.

scaprile commented 1 year ago

Also, I'm not trying to send a broadcast via mongoose, I'm trying to reply to a broadcast packet I received. Not sure if I made that clear above

No, I didn't get it, sorry about that. You should be able to reply to a broadcast packet. mg_connect() should definitely send something Did you try a working example for your hardware ? If so, and it worked, please post a network capture file, filter on your device MAC and broadcast MAC so we can try to see if something goes out. Do you know of any simulator or easy setup that we can try to quickly reproduce this ?

rickbassham commented 1 year ago

Here's an example working on the same hardware, using the Arduino framework and platformio.

https://gist.github.com/rickbassham/2912a558ec9cc8e0f86aa776503eb25c

scaprile commented 1 year ago

I mean something using that protocol that can run on a computer

rickbassham commented 1 year ago

For the thing that sends the broadcast? if so, in python:

pip install alpyca
from alpaca.discovery import search_ipv4

found = search_ipv4()

print(found)

Relevant code for the search_ipv4 function is here.

scaprile commented 1 year ago

If you look at your Wireshark captures, you should see the UDP response going out, though destination MAC is all zeroes. This shouldn't be so, we'll check

rickbassham commented 1 year ago

Just tested and this fix works. Thank you!

scaprile commented 1 year ago

You're welcome! (I tested before submitting)

WRT MG_EV_RESOLVE: It is fired when resolving, that means you have to mg_connect to get that event, that is why you didn't get called.

Now you can also send local broadcasts and multicasts. Receiving multicast is not supported (yet?).