diff options
Diffstat (limited to 'extmod/modlwip.c')
-rw-r--r-- | extmod/modlwip.c | 130 |
1 files changed, 120 insertions, 10 deletions
diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 1ebcd8923c..090e1005a8 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -43,6 +43,13 @@ #include "lwip/udp.h" //#include "lwip/raw.h" #include "lwip/dns.h" +#include "lwip/tcp_impl.h" + +#if 0 // print debugging info +#define DEBUG_printf DEBUG_printf +#else // don't print debugging info +#define DEBUG_printf(...) (void)0 +#endif // For compatibilily with older lwIP versions. #ifndef ip_set_option @@ -228,6 +235,7 @@ typedef struct _lwip_socket_obj_t { struct pbuf *pbuf; struct tcp_pcb *connection; } incoming; + mp_obj_t callback; byte peer[4]; mp_uint_t peer_port; mp_uint_t timeout; @@ -255,6 +263,12 @@ static inline void poll_sockets(void) { /*******************************************************************************/ // Callback functions for the lwIP raw API. +static inline void exec_user_callback(lwip_socket_obj_t *socket) { + if (socket->callback != MP_OBJ_NULL) { + mp_call_function_1_protected(socket->callback, socket); + } +} + // Callback for incoming UDP packets. We simply stash the packet and the source address, // in case we need it for recvfrom. STATIC void _lwip_udp_incoming(void *arg, struct udp_pcb *upcb, struct pbuf *p, ip_addr_t *addr, u16_t port) { @@ -303,11 +317,13 @@ STATIC err_t _lwip_tcp_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { tcp_recv(newpcb, _lwip_tcp_recv_unaccepted); if (socket->incoming.connection != NULL) { + DEBUG_printf("_lwip_tcp_accept: Tried to queue >1 pcb waiting for accept\n"); // We need to handle this better. This single-level structure makes the // backlog setting kind of pointless. FIXME return ERR_BUF; } else { socket->incoming.connection = newpcb; + exec_user_callback(socket); return ERR_OK; } } @@ -318,13 +334,18 @@ STATIC err_t _lwip_tcp_recv(void *arg, struct tcp_pcb *tcpb, struct pbuf *p, err if (p == NULL) { // Other side has closed connection. + DEBUG_printf("_lwip_tcp_recv[%p]: other side closed connection\n", socket); socket->state = STATE_PEER_CLOSED; + exec_user_callback(socket); return ERR_OK; } else if (socket->incoming.pbuf != NULL) { // No room in the inn, let LWIP know it's still responsible for delivery later return ERR_BUF; } socket->incoming.pbuf = p; + + exec_user_callback(socket); + return ERR_OK; } @@ -359,7 +380,10 @@ STATIC mp_uint_t lwip_udp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui pbuf_free(p); - if (err != ERR_OK) { + // udp_sendto can return 1 on occasion for ESP8266 port. It's not known why + // but it seems that the send actually goes through without error in this case. + // So we treat such cases as a success until further investigation. + if (err != ERR_OK && err != 1) { *_errno = error_lookup_table[-err]; return -1; } @@ -401,15 +425,27 @@ STATIC mp_uint_t lwip_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ return (mp_uint_t) result; } +// For use in stream virtual methods +#define STREAM_ERROR_CHECK(socket) \ + if (socket->state < 0) { \ + *_errno = error_lookup_table[-socket->state]; \ + return MP_STREAM_ERROR; \ + } \ + assert(socket->pcb.tcp); + + // Helper function for send/sendto to handle TCP packets STATIC mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_uint_t len, int *_errno) { + // Check for any pending errors + STREAM_ERROR_CHECK(socket); + u16_t available = tcp_sndbuf(socket->pcb.tcp); if (available == 0) { // Non-blocking socket if (socket->timeout == 0) { *_errno = EAGAIN; - return -1; + return MP_STREAM_ERROR; } mp_uint_t start = mp_hal_ticks_ms(); @@ -422,15 +458,13 @@ STATIC mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui while (socket->state >= STATE_CONNECTED && (available = tcp_sndbuf(socket->pcb.tcp)) < 16) { if (socket->timeout != -1 && mp_hal_ticks_ms() - start > socket->timeout) { *_errno = ETIMEDOUT; - return -1; + return MP_STREAM_ERROR; } poll_sockets(); } - if (socket->state < 0) { - *_errno = error_lookup_table[-socket->state]; - return -1; - } + // While we waited, something could happen + STREAM_ERROR_CHECK(socket); } u16_t write_len = MIN(available, len); @@ -439,7 +473,7 @@ STATIC mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui if (err != ERR_OK) { *_errno = error_lookup_table[-err]; - return -1; + return MP_STREAM_ERROR; } return write_len; @@ -447,10 +481,16 @@ STATIC mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui // Helper function for recv/recvfrom to handle TCP packets STATIC mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_t len, int *_errno) { + // Check for any pending errors + STREAM_ERROR_CHECK(socket); + if (socket->incoming.pbuf == NULL) { // Non-blocking socket if (socket->timeout == 0) { + if (socket->state == STATE_PEER_CLOSED) { + return 0; + } *_errno = EAGAIN; return -1; } @@ -463,6 +503,7 @@ STATIC mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ } poll_sockets(); } + if (socket->state == STATE_PEER_CLOSED) { if (socket->incoming.pbuf == NULL) { // socket closed and no data left in buffer @@ -475,6 +516,8 @@ STATIC mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ } } + assert(socket->pcb.tcp != NULL); + struct pbuf *p = socket->incoming.pbuf; if (socket->leftover_count == 0) { @@ -515,6 +558,7 @@ STATIC mp_obj_t lwip_socket_make_new(const mp_obj_type_t *type, mp_uint_t n_args socket->base.type = (mp_obj_t)&lwip_socket_type; socket->domain = MOD_NETWORK_AF_INET; socket->type = MOD_NETWORK_SOCK_STREAM; + socket->callback = MP_OBJ_NULL; if (n_args >= 1) { socket->domain = mp_obj_get_int(args[0]); if (n_args >= 2) { @@ -569,6 +613,7 @@ STATIC mp_obj_t lwip_socket_close(mp_obj_t self_in) { socket_is_listener = true; } if (tcp_close(socket->pcb.tcp) != ERR_OK) { + DEBUG_printf("lwip_close: had to call tcp_abort()\n"); tcp_abort(socket->pcb.tcp); } break; @@ -689,6 +734,7 @@ STATIC mp_obj_t lwip_socket_accept(mp_obj_t self_in) { socket2->timeout = socket->timeout; socket2->state = STATE_CONNECTED; socket2->leftover_count = 0; + socket2->callback = MP_OBJ_NULL; tcp_arg(socket2->pcb.tcp, (void*)socket2); tcp_err(socket2->pcb.tcp, _lwip_tcp_error); tcp_recv(socket2->pcb.tcp, _lwip_tcp_recv); @@ -918,13 +964,55 @@ STATIC mp_obj_t lwip_socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_2(lwip_socket_recvfrom_obj, lwip_socket_recvfrom); +STATIC mp_obj_t lwip_socket_sendall(mp_obj_t self_in, mp_obj_t buf_in) { + lwip_socket_obj_t *socket = self_in; + lwip_socket_check_connected(socket); + + int _errno; + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ); + + mp_uint_t ret = 0; + switch (socket->type) { + case MOD_NETWORK_SOCK_STREAM: { + if (socket->timeout == 0) { + // Behavior of sendall() for non-blocking sockets isn't explicitly specified. + // But it's specified that "On error, an exception is raised, there is no + // way to determine how much data, if any, was successfully sent." Then, the + // most useful behavior is: check whether we will be able to send all of input + // data without EAGAIN, and if won't be, raise it without sending any. + if (bufinfo.len > tcp_sndbuf(socket->pcb.tcp)) { + nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(EAGAIN))); + } + } + // TODO: In CPython3.5, socket timeout should apply to the + // entire sendall() operation, not to individual send() chunks. + while (bufinfo.len != 0) { + ret = lwip_tcp_send(socket, bufinfo.buf, bufinfo.len, &_errno); + if (ret == -1) { + nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(_errno))); + } + bufinfo.len -= ret; + bufinfo.buf = (char*)bufinfo.buf + ret; + } + break; + } + case MOD_NETWORK_SOCK_DGRAM: + mp_not_implemented(""); + break; + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(lwip_socket_sendall_obj, lwip_socket_sendall); + STATIC mp_obj_t lwip_socket_settimeout(mp_obj_t self_in, mp_obj_t timeout_in) { lwip_socket_obj_t *socket = self_in; mp_uint_t timeout; if (timeout_in == mp_const_none) { timeout = -1; } else { - #if MICROPY_PY_BUILTIN_FLOAT + #if MICROPY_PY_BUILTINS_FLOAT timeout = 1000 * mp_obj_get_float(timeout_in); #else timeout = 1000 * mp_obj_get_int(timeout_in); @@ -950,8 +1038,20 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(lwip_socket_setblocking_obj, lwip_socket_setblo STATIC mp_obj_t lwip_socket_setsockopt(mp_uint_t n_args, const mp_obj_t *args) { (void)n_args; // always 4 lwip_socket_obj_t *socket = args[0]; + + int opt = mp_obj_get_int(args[2]); + if (opt == 20) { + if (args[3] == mp_const_none) { + socket->callback = MP_OBJ_NULL; + } else { + socket->callback = args[3]; + } + return mp_const_none; + } + + // Integer options mp_int_t val = mp_obj_get_int(args[3]); - switch (mp_obj_get_int(args[2])) { + switch (opt) { case SOF_REUSEADDR: // Options are common for UDP and TCP pcb's. if (val) { @@ -1010,6 +1110,7 @@ STATIC const mp_map_elem_t lwip_socket_locals_dict_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_recv), (mp_obj_t)&lwip_socket_recv_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_sendto), (mp_obj_t)&lwip_socket_sendto_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_recvfrom), (mp_obj_t)&lwip_socket_recvfrom_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_sendall), (mp_obj_t)&lwip_socket_sendall_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_settimeout), (mp_obj_t)&lwip_socket_settimeout_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_setblocking), (mp_obj_t)&lwip_socket_setblocking_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_setsockopt), (mp_obj_t)&lwip_socket_setsockopt_obj }, @@ -1143,6 +1244,14 @@ STATIC mp_obj_t lwip_getaddrinfo(mp_obj_t host_in, mp_obj_t port_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_2(lwip_getaddrinfo_obj, lwip_getaddrinfo); +// Debug functions + +STATIC mp_obj_t lwip_print_pcbs() { + tcp_debug_print_pcbs(); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_0(lwip_print_pcbs_obj, lwip_print_pcbs); + #ifdef MICROPY_PY_LWIP STATIC const mp_map_elem_t mp_module_lwip_globals_table[] = { @@ -1150,6 +1259,7 @@ STATIC const mp_map_elem_t mp_module_lwip_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_reset), (mp_obj_t)&mod_lwip_reset_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_callback), (mp_obj_t)&mod_lwip_callback_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_getaddrinfo), (mp_obj_t)&lwip_getaddrinfo_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_print_pcbs), (mp_obj_t)&lwip_print_pcbs_obj }, // objects { MP_OBJ_NEW_QSTR(MP_QSTR_socket), (mp_obj_t)&lwip_socket_type }, #ifdef MICROPY_PY_LWIP_SLIP |