diff --git a/app/include/user_config.h b/app/include/user_config.h index 82b0c1cd..928d5884 100644 --- a/app/include/user_config.h +++ b/app/include/user_config.h @@ -136,6 +136,9 @@ //#define TIMER_SUSPEND_ENABLE //#define PMSLEEP_ENABLE +// The net module optionally offers net info functionnality. Uncomment the following +// to enable the functionnality. +#define NET_PING_ENABLE // The WiFi module optionally offers an enhanced level of WiFi connection // management, using internal timer callbacks. Whilst many Lua developers diff --git a/app/modules/net.c b/app/modules/net.c index 73496989..74356d26 100644 --- a/app/modules/net.c +++ b/app/modules/net.c @@ -20,6 +20,8 @@ #include "lwip/udp.h" #include "lwip/dhcp.h" +#include "net_ping.h" + typedef enum net_type { TYPE_TCP_SERVER = 0, TYPE_TCP_CLIENT, @@ -1070,6 +1072,9 @@ LROT_BEGIN(net, NULL, 0) LROT_FUNCENTRY( ifinfo, net_ifinfo ) LROT_FUNCENTRY( multicastJoin, net_multicastJoin ) LROT_FUNCENTRY( multicastLeave, net_multicastLeave ) +#ifdef NET_PING_ENABLE + LROT_FUNCENTRY( ping, net_ping ) +#endif LROT_TABENTRY( dns, net_dns_map ) #ifdef TLS_MODULE_PRESENT LROT_TABENTRY( cert, tls_cert ) diff --git a/app/modules/net_ping.c b/app/modules/net_ping.c new file mode 100644 index 00000000..4cfbe7f4 --- /dev/null +++ b/app/modules/net_ping.c @@ -0,0 +1,155 @@ +// *************************************************************************** +// net_ping functionnality for ESP8266 with nodeMCU +// +// Written by Lukas Voborsky (@voborsky) with great help by TerryE +// *************************************************************************** + +// #define NODE_DEBUG + +#include "net_ping.h" + +#include "module.h" +#include "lauxlib.h" + +#include "lwip/ip_addr.h" +#include "espconn.h" +#include "lwip/dns.h" +#include "lwip/app/ping.h" + +/* +ping_opt needs to be the first element of the structure. It is a workaround to pass the +callback reference and self_ref to the ping_received function. Pointer the ping_option +structure is equal to the pointer to net_ping_t structure. +*/ +typedef struct { + struct ping_option ping_opt; + uint32_t ping_callback_ref; + } net_ping_t; +typedef net_ping_t* ping_t; + +/* + * ping_received_sent(pingresp) +*/ +#define LuaCBreceivedfunc lua_upvalueindex(1) +#define LuaCBsentfunc lua_upvalueindex(2) +#define nipUD lua_upvalueindex(3) + +static int ping_received_sent(lua_State *L) { + struct ping_resp *resp = (struct ping_resp *) lua_touserdata (L, 1); + ping_t nip = (ping_t) lua_touserdata (L, nipUD); + + NODE_DBG("[net_info ping_received_sent] nip = %p\n", nip); + + if (resp == NULL) { /* resolution failed so call the CB with 0 byte count to flag this */ + luaL_unref(L, LUA_REGISTRYINDEX, nip->ping_callback_ref); + lua_pushvalue(L, LuaCBreceivedfunc); + lua_pushinteger(L, 0); + luaL_pcallx(L, 1, 0); + return 0; + } + char ipaddrstr[16]; + ipaddr_ntoa_r((ip_addr_t *) &nip->ping_opt.ip, ipaddrstr, sizeof(ipaddrstr)); + + if (resp->total_count == 0) { /* processing receive response */ + NODE_DBG("[ping_received] %s: resp_time=%d seqno=%d bytes=%d ping_err=%d\n", + ipaddrstr, resp->resp_time, resp->seqno, resp->bytes, resp->ping_err); + lua_pushvalue(L, LuaCBreceivedfunc); + lua_pushinteger(L, resp->bytes); + lua_pushstring(L, ipaddrstr); + lua_pushinteger(L, resp->seqno); + lua_pushinteger(L, resp->ping_err == 0 ? resp->resp_time: -1); + luaL_pcallx(L, 4, 0); + } else { /* processing sent response */ + NODE_DBG("[ping_sent] %s: total_count=%d timeout_count=%d " + "total_bytes=%d total_time=%d\n", + ipaddrstr, resp->total_count, resp->timeout_count, + resp->total_bytes, resp->total_time); + + lua_pushvalue(L, LuaCBsentfunc); + if lua_isfunction(L, -1) { + lua_pushstring(L, ipaddrstr); + lua_pushinteger(L, resp->total_count); + lua_pushinteger(L, resp->timeout_count); + lua_pushinteger(L, resp->total_bytes); + lua_pushinteger(L, resp->total_time); + luaL_pcallx(L, 5, 0); + } + luaL_unref(L, LUA_REGISTRYINDEX, nip->ping_callback_ref); /* unregister the closure */ + } + return 0; +} + + +/* + * Wrapper to call ping_received_sent(pingresp) + */ +static void ping_CB(net_ping_t *nip, struct ping_resp *pingresp) { + NODE_DBG("[net_info ping_CB] nip = %p, nip->ping_callback_ref = %p, pingresp= %p\n", nip, nip->ping_callback_ref, pingresp); + lua_State *L = lua_getstate(); + lua_rawgeti(L, LUA_REGISTRYINDEX, nip->ping_callback_ref); + lua_pushlightuserdata(L, pingresp); + lua_call(L, 1, 0); // call the closure (ping_received_sent) +} + +/* + * Wrapper to call ping_start using fully resolve IP4 address + */ +static void net_ping_raw(const char *name, ip_addr_t *ipaddr, ping_t nip) { + NODE_DBG("[net_ping_raw] name = %s, ipaddr= %x\n", name, ipaddr); + if (ipaddr) { + char ipaddrstr[16]; + ipaddr_ntoa_r(ipaddr, ipaddrstr, sizeof(ipaddrstr)); + NODE_DBG("[net_ping_raw] ip: %s\n", ipaddrstr); + } + lua_State *L = lua_getstate(); + + if (!ipaddr || ipaddr->addr == 0xFFFFFFFF) { + ping_CB(nip, NULL); /* A NULL pinresp flags DNS resolution failure */ + return; + } + + nip->ping_opt.ip = ipaddr->addr; + NODE_DBG("[net_ping_raw] calling ping_start\n"); + if (!ping_start(&(nip->ping_opt))) { + luaL_unref(L, LUA_REGISTRYINDEX, nip->ping_callback_ref); + luaL_error(L, "memory allocation error: cannot start ping"); + } +} + +// Lua: net.ping(domain, [count], callback) +int net_ping(lua_State *L) +{ + ip_addr_t addr; + + // retrieve function parameters + const char *ping_target = luaL_checkstring(L, 1); + bool isf2 = lua_isfunction(L, 2); + lua_Integer l_count = isf2 ? 0: luaL_optinteger(L, 2, 0); /* use ping_start() default */ + lua_settop(L, isf2 ? 3 : 4); + luaL_argcheck(L, lua_isfunction(L, -2), -2, "no received callback specified"); + luaL_argcheck(L, lua_isfunction(L, -1) || lua_isnil(L, -1), -1, "invalid sent callback, function expected"); + + ping_t nip = (ping_t) memset(lua_newuserdata(L, sizeof(*nip)), 0, sizeof(*nip)); + + /* Register C closure with 3 Upvals: (1) Lua CB receive function; (2) Lua CB sent function; (3) nip Userdata */ + lua_pushcclosure(L, ping_received_sent, 3); // stack has 2 callbacks and nip UD; [-3, +1, m] + + nip->ping_callback_ref = luaL_ref(L, LUA_REGISTRYINDEX); // registers the closure to registry [-1, +0, m] + nip->ping_opt.count = l_count; + nip->ping_opt.coarse_time = 0; + nip->ping_opt.recv_function = (ping_recv_function) &ping_CB; + nip->ping_opt.sent_function = (ping_sent_function) &ping_CB; + + NODE_DBG("[net_ping] nip = %p, nip->ping_callback_ref = %p\n", nip, nip->ping_callback_ref); + + err_t err = dns_gethostbyname(ping_target, &addr, (dns_found_callback) net_ping_raw, nip); + if (err != ERR_OK && err != ERR_INPROGRESS) { + luaL_unref(L, LUA_REGISTRYINDEX, nip->ping_callback_ref); + return luaL_error(L, "lwip error %d", err); + } + if (err == ERR_OK) { + NODE_DBG("[net_ping] No DNS resolution needed\n"); + net_ping_raw(ping_target, &addr, nip); + } + return 0; +} diff --git a/app/modules/net_ping.h b/app/modules/net_ping.h new file mode 100644 index 00000000..ac2c2b0d --- /dev/null +++ b/app/modules/net_ping.h @@ -0,0 +1,3 @@ +#ifdef NET_PING_ENABLE +int net_ping(lua_State *L); +#endif diff --git a/docs/modules/net.md b/docs/modules/net.md index f6d6901f..3712fdb1 100644 --- a/docs/modules/net.md +++ b/docs/modules/net.md @@ -626,6 +626,74 @@ Sets the IP of the DNS server used to resolve hostnames. Default: resolver1.open #### See also [`net.dns:getdnsserver()`](#netdnsgetdnsserver) + +### net.ping() + +Pings a server. A callback function is called when response is or is not received. Summary statistics can be retrieved via the second callback. + +The function can be disabled by commenting `NET_PING_ENABLE` macro in `user_config.h` when more compact build is needed. + +#### Syntax +`net.ping(domain, [count], callback_received, [callback_sent])` + +#### Parameters +- `domain` destination domain or IP address +- `count` number of ping packets to be sent (optional parameter, default value is 4) +- `callback_received(bytes, ipaddr, seqno, rtt)` callback function which is invoked when response is received where + - `bytes` number of bytes received from destination server (0 means no response) + - `ipaddr` destination server IP address + - `seqno` ICMP sequence number + - `rtt` round trip time in ms +If domain name cannot be resolved callback is invoked with `bytes` parameter equal to 0 (i.e. no response) and `nil` values for all other parameters. + +- `callback_sent(ipaddr, total_count, timeout_count, total_bytes, total_time)` callback function which is invoked when response is received where + - `ipaddrstr` destination server IP address + - `total_count` total number of packets sent + - `timeout_count` total number of packets lost (not received) + - `total_bytes` total number of bytes received from destination server + - `total_time` total time to perform ping + +#### Returns +`nil` + +#### Example +```lua +net.ping("www.nodemcu.com", function (b, ip, sq, tm) + if ip then print(("%d bytes from %s, icmp_seq=%d time=%dms"):format(b, ip, sq, tm)) else print("Invalid IP address") end + end) +net.ping("www.nodemcu.com", 10, function (b, ip, sq, tm) + if ip then print(("%d bytes from %s, icmp_seq=%d time=%dms"):format(b, ip, sq, tm)) else print("Invalid IP address") end + end) +net.ping("www.nodemcu.com", function (b, ip, sq, tm) + if ip then print(("%d bytes from %s, icmp_seq=%d time=%dms"):format(b, ip, sq, tm)) else print("Invalid IP address") end + end, + function (ip, tc, toc, tb, tt) + print(("--- %s ping statistics ---\n%d packets transmitted, %d received, %d%% packet loss, time %dms"):format(ip, tc, tc-toc, toc/tc*100, tt)) + end) +``` + +Multiple pings can start in short sequence thought if the new ping overlaps with the previous one the first stops receiving answers, i.e. +```lua +function ping_resp(b, ip, sq, tm) + print(string.format("%d bytes from %s, icmp_seq=%d time=%dms", b, ip, sq, tm)) +end + +net.ping("8.8.8.8", 4, ping_resp) +tmr.create():alarm(1000, tmr.ALARM_SINGLE, function() net.ping("8.8.4.4", 4, ping_resp) end) +``` +gives +``` +32 bytes from 8.8.8.8, icmp_seq=9 time=14ms +32 bytes from 8.8.8.8, icmp_seq=10 time=9ms +32 bytes from 8.8.4.4, icmp_seq=11 time=6ms +32 bytes from 8.8.4.4, icmp_seq=13 time=12ms +0 bytes from 8.8.8.8, icmp_seq=0 time=0ms -- no more answers received +32 bytes from 8.8.4.4, icmp_seq=15 time=16ms +0 bytes from 8.8.8.8, icmp_seq=0 time=0ms -- no more answers received +32 bytes from 8.8.4.4, icmp_seq=16 time=7ms +``` + + # net.cert Module This part gone to the [TLS](tls.md) module, link kept for backward compatibility.