summaryrefslogtreecommitdiffstatshomepage
path: root/extmod/modlwip.c
diff options
context:
space:
mode:
Diffstat (limited to 'extmod/modlwip.c')
-rw-r--r--extmod/modlwip.c130
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