nodemcu / nodemcu-firmware

Lua based interactive firmware for ESP8266, ESP8285 and ESP32
https://nodemcu.readthedocs.io
MIT License
7.66k stars 3.12k forks source link

Stored clients in an UDP server #1373

Closed ThibaudCognat closed 7 years ago

ThibaudCognat commented 8 years ago

Missing feature

In the net module, it is impossible, or seems to be to keep a track on the UDP clients connected to the UDP server. A call to the method send actually sends a message to the last client from which it received a message. An interesting feature would be to be able to get the IP and port of a sender, and this way, select which client to send an response to.

Justification

This feature would be useful in a case where a server listens to requests, and must be able to answer to multiple clients, not necessarily in the order of the requests reception.

Workarounds

There is no apparent workaround, and that's why we are blocked in our implementations.

djphoenix commented 8 years ago

Have spent some time and realized it (with some code usage from PR #1233). Try out this patch:

diff --git a/app/lwip/app/espconn_udp.c b/app/lwip/app/espconn_udp.c
index 77ef471..9c8bc8e 100644
--- a/app/lwip/app/espconn_udp.c
+++ b/app/lwip/app/espconn_udp.c
@@ -308,6 +308,11 @@ espconn_udp_recv(void *arg, struct udp_pcb *upcb, struct pbuf *p,
        wifi_get_ip_info(0, &ipconfig);
    }

+  precv->pespconn->proto.udp->remote_ip[0] = ip4_addr1_16(addr);
+  precv->pespconn->proto.udp->remote_ip[1] = ip4_addr2_16(addr);
+  precv->pespconn->proto.udp->remote_ip[2] = ip4_addr3_16(addr);
+  precv->pespconn->proto.udp->remote_ip[3] = ip4_addr4_16(addr);
+  precv->pespconn->proto.udp->remote_port = port;
    precv->pespconn->proto.udp->local_ip[0] = ip4_addr1_16(&ipconfig.ip);
    precv->pespconn->proto.udp->local_ip[1] = ip4_addr2_16(&ipconfig.ip);
    precv->pespconn->proto.udp->local_ip[2] = ip4_addr3_16(&ipconfig.ip);
diff --git a/app/modules/net.c b/app/modules/net.c
index 8181b94..c515269 100644
--- a/app/modules/net.c
+++ b/app/modules/net.c
@@ -1067,26 +1067,44 @@ static int net_send( lua_State* L, const char* mt )
   NODE_DBG(" sending data.\n");
 #endif

-  const char *payload = luaL_checklstring( L, 2, &l );
+  size_t stack = 2;
+
+  const char *payload = luaL_checklstring( L, stack, &l );
   if (l>1460 || payload == NULL)
     return luaL_error( L, "need <1460 payload" );
+  stack++;

-  if (lua_type(L, 3) == LUA_TFUNCTION || lua_type(L, 3) == LUA_TLIGHTFUNCTION){
-    lua_pushvalue(L, 3);  // copy argument (func) to the top of stack
-    if(nud->cb_send_ref != LUA_NOREF)
-      luaL_unref(L, LUA_REGISTRYINDEX, nud->cb_send_ref);
-    nud->cb_send_ref = luaL_ref(L, LUA_REGISTRYINDEX);
-  }
   // SDK 1.4.0 changed behaviour, for UDP server need to look up remote ip/port
   if (isserver && pesp_conn->type == ESPCONN_UDP)
   {
-    remot_info *pr = 0;
-    if (espconn_get_connection_info (pesp_conn, &pr, 0) != ESPCONN_OK)
-      return luaL_error (L, "remote ip/port unavailable");
-    pesp_conn->proto.udp->remote_port = pr->remote_port;
-    os_memmove (pesp_conn->proto.udp->remote_ip, pr->remote_ip, 4);
+    
+    if ( lua_isnumber(L,stack) && lua_isstring(L,stack+1) )
+    {
+      size_t il = 16;
+      const unsigned short port = luaL_checkinteger( L, stack );
+      const char *domain = luaL_checklstring( L, stack+1, &il );
+
+      ip_addr_t ipaddr;
+      pesp_conn->proto.udp->remote_port = port;
+      ipaddr.addr = ipaddr_addr(domain);
+      os_memmove (pesp_conn->proto.udp->remote_ip, &ipaddr.addr, 4);
+      stack += 2;
+    } else {
+      remot_info *pr = 0;
+      if (espconn_get_connection_info (pesp_conn, &pr, 0) != ESPCONN_OK)
+        return luaL_error (L, "remote ip/port unavailable");
+      pesp_conn->proto.udp->remote_port = pr->remote_port;
+      os_memmove (pesp_conn->proto.udp->remote_ip, pr->remote_ip, 4);
+    }
+
     // The remot_info apparently should *not* be os_free()d, fyi
   }
+  if (lua_type(L, stack) == LUA_TFUNCTION || lua_type(L, stack) == LUA_TLIGHTFUNCTION){
+    lua_pushvalue(L, stack);  // copy argument (func) to the top of stack
+    if(nud->cb_send_ref != LUA_NOREF)
+      luaL_unref(L, LUA_REGISTRYINDEX, nud->cb_send_ref);
+    nud->cb_send_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+  }
 #ifdef CLIENT_SSL_ENABLE
   if(nud->secure)
     espconn_secure_sent(pesp_conn, (unsigned char *)payload, l);
@@ -1355,28 +1373,45 @@ static int net_socket_unhold( lua_State* L )
 }

 // Lua: ip,port = sk:getpeer()
-static int net_socket_getpeer( lua_State* L )
+static int net_getpeer( lua_State* L, const char *mt)
 {
   lnet_userdata *nud;
-  const char *mt = "net.socket";
+  int remote_port = 0;
+  uint8 *remote_ip;
+  
   nud = (lnet_userdata *)luaL_checkudata(L, 1, mt);
   luaL_argcheck(L, nud, 1, "Server/Socket expected");

-  if(nud!=NULL && nud->pesp_conn!=NULL ){
+  if (nud->pesp_conn!=NULL){
+    if (nud->pesp_conn->type == ESPCONN_UDP) {
+      remote_ip   = nud->pesp_conn->proto.udp->remote_ip;
+      remote_port = nud->pesp_conn->proto.udp->remote_port;
+    } else if (nud->pesp_conn->type == ESPCONN_TCP) {
+      luaL_argcheck(L, c_strcmp(mt, "net.socket")==0, 1, "Cannot getpeer() a TCP server");
+      remote_ip   = nud->pesp_conn->proto.tcp->remote_ip;
+      remote_port = nud->pesp_conn->proto.tcp->remote_port;
+    }        
+    if (remote_port != 0) {
       char temp[20] = {0};
-      c_sprintf(temp, IPSTR, IP2STR( &(nud->pesp_conn->proto.tcp->remote_ip) ) );
-      if ( nud->pesp_conn->proto.tcp->remote_port != 0 ) {
-        lua_pushstring( L, temp );
-        lua_pushinteger( L, nud->pesp_conn->proto.tcp->remote_port );
-      } else {
-        lua_pushnil( L );
-        lua_pushnil( L );
-      }
-  } else {
-      lua_pushnil( L );
-      lua_pushnil( L );
+      c_sprintf(temp, IPSTR, IP2STR(remote_ip));
+      lua_pushstring( L, temp );
+      lua_pushinteger( L, remote_port );
+      return 2;
+    } 
   }
-  return 2;
+  return 0;
+}
+
+// Lua: ip,port = sk:getpeer()
+static int net_udpserver_getpeer( lua_State* L )
+{
+  return net_getpeer(L, "net.server");
+}
+
+// Lua: ip,port = sk:getpeer()
+static int net_socket_getpeer( lua_State* L )
+{
+  return net_getpeer(L, "net.socket");
 }

 // Lua: socket:dns( string, function(ip) )
@@ -1708,6 +1743,7 @@ static const LUA_REG_TYPE net_server_map[] = {
   { LSTRKEY( "close" ),   LFUNCVAL( net_server_close ) },
   { LSTRKEY( "on" ),      LFUNCVAL( net_udpserver_on ) },
   { LSTRKEY( "send" ),    LFUNCVAL( net_udpserver_send ) },
+  { LSTRKEY( "getpeer" ), LFUNCVAL( net_udpserver_getpeer ) },
 //{ LSTRKEY( "delete" ),  LFUNCVAL( net_server_delete ) },
   { LSTRKEY( "__gc" ),    LFUNCVAL( net_server_delete ) },
   { LSTRKEY( "__index" ), LROVAL( net_server_map ) },
ThibaudCognat commented 8 years ago

Thanks for the quick and effective response

marcelstoer commented 8 years ago

@djphoenix @ThibaudCognat can one of you prepare a PR for that?

djphoenix commented 8 years ago

@marcelstoer Part of this (got from #1233) was rejected (actually, not understood why). :sendto method was discussed in another place and closed because deprecation of espconn layer (maybe at "net module reorganization" or "RTOS SDK", not remember...)

Diff above is from my local repository, it has many patches that not merged till now (with rboot and another enhancements).

robertfoss commented 8 years ago

@djphoenix I too would like to see a PR. And some documentation in the PR.

djphoenix commented 7 years ago

I think this issue was resolved with #1379 -> closed. @ThibaudCognat @robertfoss feel free to try latest dev so.