From 7cef6fcc7bb32d7a66f08011f67958dc7e3520d4 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Sat, 24 Mar 2018 08:07:00 +0800 Subject: [PATCH] udp --- READMEs/README.coding.md | 44 ++++++++++++++++++++++- lib/context.c | 21 ++++++++--- lib/libwebsockets.c | 72 +++++++++++++++++++++++++++++++++++-- lib/libwebsockets.h | 45 +++++++++++++++++++++-- lib/output.c | 23 ++++++++++-- lib/private-libwebsockets.h | 3 ++ lib/server/peer-limits.c | 6 ++-- lib/server/server.c | 13 ++++--- 8 files changed, 206 insertions(+), 21 deletions(-) diff --git a/READMEs/README.coding.md b/READMEs/README.coding.md index e844a8f8..9ac87e6b 100644 --- a/READMEs/README.coding.md +++ b/READMEs/README.coding.md @@ -743,7 +743,9 @@ callbacks on the named protocol starting with LWS_CALLBACK_RAW_ADOPT_FILE. -`protocol-lws-raw-test` plugin provides a method for testing this with +The minimal example `raw/minimal-raw-file` demonstrates how to use it. + +`protocol-lws-raw-test` plugin also provides a method for testing this with `libwebsockets-test-server-v2.0`: The plugin creates a FIFO on your system called "/tmp/lws-test-raw" @@ -827,6 +829,46 @@ and in another window, connect to it using the test client The connection should succeed, and text typed in the netcat window (including a CRLF) will be received in the client. +@section rawudp RAW UDP socket integration + +Lws provides an api to create, optionally bind, and adopt a RAW UDP +socket (RAW here means an uninterpreted normal UDP socket, not a +"raw socket"). + +``` +LWS_VISIBLE LWS_EXTERN struct lws * +lws_create_adopt_udp(struct lws_vhost *vhost, int port, int flags, + const char *protocol_name, struct lws *parent_wsi); +``` + +`flags` should be `LWS_CAUDP_BIND` if the socket will receive packets. + +The callbacks `LWS_CALLBACK_RAW_ADOPT`, `LWS_CALLBACK_RAW_CLOSE`, +`LWS_CALLBACK_RAW_RX` and `LWS_CALLBACK_RAW_WRITEABLE` apply to the +wsi. But UDP is different than TCP in some fundamental ways. + +For receiving on a UDP connection, data becomes available at +`LWS_CALLBACK_RAW_RX` as usual, but because there is no specific +connection with UDP, it is necessary to also get the source address of +the data separately, using `struct lws_udp * lws_get_udp(wsi)`. +You should take a copy of the `struct lws_udp` itself (not the +pointer) and save it for when you want to write back to that peer. + +Writing is also a bit different for UDP. By default, the system has no +idea about the receiver state and so asking for a `callback_on_writable()` +always believes that the socket is writeable... the callback will +happen next time around the event loop. + +With UDP, there is no single "connection". You need to write with sendto() and +direct the packets to a specific destination. To return packets to a +peer who sent something earlier and you copied his `struct lws_udp`, you +use the .sa and .salen members as the last two parameters of the sendto(). + +The kernel may not accept to buffer / write everything you wanted to send. +So you are responsible to watch the result of sendto() and resend the +unsent part next time (which may involve adding new protocol headers to +the remainder depending on what you are doing). + @section ecdh ECDH Support ECDH Certs are now supported. Enable the CMake option diff --git a/lib/context.c b/lib/context.c index 310cfe39..eb3109eb 100644 --- a/lib/context.c +++ b/lib/context.c @@ -565,6 +565,7 @@ lws_create_vhost(struct lws_context *context, #endif struct lws_protocols *lwsp; int m, f = !info->pvo; + char buf[20]; #ifdef LWS_HAVE_GETENV char *p; #endif @@ -721,10 +722,22 @@ lws_create_vhost(struct lws_context *context, vh->name, vh->iface, vh->count_protocols); } else #endif - lwsl_notice("Creating Vhost '%s' port %d, %d protocols, IPv6 %s\n", - vh->name, info->port, vh->count_protocols, - LWS_IPV6_ENABLED(vh) ? "on" : "off"); - + { + switch(info->port) { + case CONTEXT_PORT_NO_LISTEN: + strcpy(buf, "(serving disabled)"); + break; + case CONTEXT_PORT_NO_LISTEN_SERVER: + strcpy(buf, "(no listener)"); + break; + default: + lws_snprintf(buf, sizeof(buf), "port %u", info->port); + break; + } + lwsl_notice("Creating Vhost '%s' %s, %d protocols, IPv6 %s\n", + vh->name, buf, vh->count_protocols, + LWS_IPV6_ENABLED(vh) ? "on" : "off"); + } mounts = info->mounts; while (mounts) { (void)mount_protocols[0]; diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 9ba3461c..fc822a65 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -80,6 +80,7 @@ __lws_free_wsi(struct lws *wsi) lws_free_set_NULL(wsi->rxflow_buffer); lws_free_set_NULL(wsi->trunc_alloc); lws_free_set_NULL(wsi->ws); + lws_free_set_NULL(wsi->udp); /* we may not have an ah, but may be on the waiting list... */ lwsl_info("ah det due to close\n"); @@ -1330,6 +1331,12 @@ lws_protocol_get(struct lws *wsi) return wsi->protocol; } +LWS_VISIBLE const struct lws_udp * +lws_get_udp(const struct lws *wsi) +{ + return wsi->udp; +} + LWS_VISIBLE struct lws * lws_get_network_wsi(struct lws *wsi) { @@ -2694,12 +2701,71 @@ lws_get_addr_scope(const char *ipaddr) } #endif +#if !defined(LWS_NO_SERVER) + +LWS_EXTERN struct lws * +lws_create_adopt_udp(struct lws_vhost *vhost, int port, int flags, + const char *protocol_name, struct lws *parent_wsi) +{ + lws_sock_file_fd_type sock; + struct addrinfo h, *r, *rp; + struct lws *wsi = NULL; + char buf[16]; + int n; + + memset(&h, 0, sizeof(h)); + h.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + h.ai_socktype = SOCK_DGRAM; + h.ai_protocol = IPPROTO_UDP; + h.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + + lws_snprintf(buf, sizeof(buf), "%u", port); + n = getaddrinfo(NULL, buf, &h, &r); + if (n) { + lwsl_info("%s: getaddrinfo error: %s\n", __func__, + gai_strerror(n)); + goto bail; + } + + for (rp = r; rp; rp = rp->ai_next) { + sock.sockfd = socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (sock.sockfd >= 0) + break; + } + if (!rp) { + lwsl_err("%s: unable to create INET socket\n", __func__); + goto bail1; + } + + if ((flags & LWS_CAUDP_BIND) && + bind(sock.sockfd, rp->ai_addr, rp->ai_addrlen) ==-1) { + lwsl_err("%s: bind failed\n", __func__); + goto bail2; + } + + wsi = lws_adopt_descriptor_vhost(vhost, LWS_ADOPT_RAW_SOCKET_UDP, sock, + protocol_name, parent_wsi); + if (!wsi) + lwsl_err("%s: udp adoption failed\n", __func__); + +bail2: + if (!wsi) + close(sock.sockfd); +bail1: + freeaddrinfo(r); + +bail: + return wsi; +} + +#endif + LWS_EXTERN void lws_restart_ws_ping_pong_timer(struct lws *wsi) { - if (!wsi->context->ws_ping_pong_interval) - return; - if (!lws_state_is_ws(wsi->state)) + if (!wsi->context->ws_ping_pong_interval || + !lws_state_is_ws(wsi->state)) return; wsi->ws->time_next_ping_check = (time_t)lws_now_secs(); diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 210ff562..a03928d6 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -4555,6 +4555,7 @@ enum pending_timeout { PENDING_TIMEOUT_KILLED_BY_PARENT = 23, PENDING_TIMEOUT_CLOSE_SEND = 24, PENDING_TIMEOUT_HOLDING_AH = 25, + PENDING_TIMEOUT_UDP_IDLE = 26, /****** add new things just above ---^ ******/ @@ -4998,7 +4999,7 @@ lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, /** * lws_get_socket_fd() - returns the socket file descriptor * - * You will not need this unless you are doing something special + * This is needed to use sendto() on UDP raw sockets * * \param wsi: Websocket connection instance */ @@ -5151,8 +5152,10 @@ typedef enum { LWS_ADOPT_ALLOW_SSL = 4, /* flag: if set requires LWS_ADOPT_SOCKET */ LWS_ADOPT_WS_PARENTIO = 8, /* flag: ws mode parent handles IO * if given must be only flag - * wsi put directly into ws mode - */ + * wsi put directly into ws mode */ + LWS_ADOPT_FLAG_UDP = 16, /* flag: socket is UDP */ + + LWS_ADOPT_RAW_SOCKET_UDP = LWS_ADOPT_SOCKET | LWS_ADOPT_FLAG_UDP, } lws_adoption_type; typedef union { @@ -5160,6 +5163,14 @@ typedef union { lws_filefd_type filefd; } lws_sock_file_fd_type; +struct lws_udp { + struct sockaddr sa; + socklen_t salen; + + struct sockaddr sa_pending; + socklen_t salen_pending; +}; + /* * lws_adopt_descriptor_vhost() - adopt foreign socket or file descriptor * if socket descriptor, should already have been accepted from listen socket @@ -5236,6 +5247,24 @@ lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd, LWS_VISIBLE LWS_EXTERN struct lws * lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, lws_sockfd_type accept_fd, const char *readbuf, size_t len); + +#define LWS_CAUDP_BIND 1 + +/** + * lws_create_adopt_udp() - create, bind and adopt a UDP socket + * + * \param vhost: lws vhost + * \param port: UDP port to bind to, -1 means unbound + * \param flags: 0 or LWS_CAUDP_NO_BIND + * \param protocol_name: Name of protocol on vhost to bind wsi to + * \param parent_wsi: NULL or parent wsi new wsi will be a child of + * + * Either returns new wsi bound to accept_fd, or closes accept_fd and + * returns NULL, having cleaned up any new wsi pieces. + * */ +LWS_VISIBLE LWS_EXTERN struct lws * +lws_create_adopt_udp(struct lws_vhost *vhost, int port, int flags, + const char *protocol_name, struct lws *parent_wsi); ///@} /** \defgroup net Network related helper APIs @@ -5632,6 +5661,16 @@ lws_get_parent(const struct lws *wsi); LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT lws_get_child(const struct lws *wsi); +/** + * lws_get_udp() - get wsi's udp struct + * + * \param wsi: lws connection + * + * Returns NULL or pointer to the wsi's UDP-specific information + */ +LWS_VISIBLE LWS_EXTERN const struct lws_udp * LWS_WARN_UNUSED_RESULT +lws_get_udp(const struct lws *wsi); + /** * lws_parent_carries_io() - mark wsi as needing to send messages via parent * diff --git a/lib/output.c b/lib/output.c index 31ccb378..be3a4e73 100644 --- a/lib/output.c +++ b/lib/output.c @@ -198,6 +198,12 @@ handle_truncated_send: wsi->trunc_len = (unsigned int)(real_len - n); memcpy(wsi->trunc_alloc, buf + n, real_len - n); + if (lws_wsi_is_udp(wsi)) { + /* stash original destination for fulfilling UDP partials */ + wsi->udp->sa_pending = wsi->udp->sa; + wsi->udp->salen_pending = wsi->udp->salen; + } + /* since something buffered, force it to get another chance to send */ lws_callback_on_writable(wsi); @@ -858,12 +864,19 @@ lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len) lws_stats_atomic_bump(context, pt, LWSSTATS_C_API_READ, 1); - n = recv(wsi->desc.sockfd, (char *)buf, len, 0); + if (lws_wsi_is_udp(wsi)) { + wsi->udp->salen = sizeof(wsi->udp->sa); + n = recvfrom(wsi->desc.sockfd, (char *)buf, len, 0, + &wsi->udp->sa, &wsi->udp->salen); + } else + n = recv(wsi->desc.sockfd, (char *)buf, len, 0); + if (n >= 0) { if (wsi->vhost) wsi->vhost->conn_stats.rx += n; lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n); lws_restart_ws_ping_pong_timer(wsi); + return n; } #if LWS_POSIX @@ -882,7 +895,13 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len) int n = 0; #if LWS_POSIX - n = send(wsi->desc.sockfd, (char *)buf, len, MSG_NOSIGNAL); + if (lws_wsi_is_udp(wsi)) { + if (wsi->trunc_len) + n = sendto(wsi->desc.sockfd, buf, len, 0, &wsi->udp->sa_pending, wsi->udp->salen_pending); + else + n = sendto(wsi->desc.sockfd, buf, len, 0, &wsi->udp->sa, wsi->udp->salen); + } else + n = send(wsi->desc.sockfd, (char *)buf, len, MSG_NOSIGNAL); // lwsl_info("%s: sent len %d result %d", __func__, len, n); if (n >= 0) return n; diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 02b22a2d..d6604a9b 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -1840,6 +1840,8 @@ struct lws_access_log { }; #endif +#define lws_wsi_is_udp(___wsi) (!!___wsi->udp) + struct lws { /* structs */ @@ -1881,6 +1883,7 @@ struct lws { #endif struct allocated_headers *ah; struct lws *ah_wait_list; + struct lws_udp *udp; unsigned char *preamble_rx; #ifndef LWS_NO_CLIENT struct client_info_stash *stash; diff --git a/lib/server/peer-limits.c b/lib/server/peer-limits.c index 4e8b3aba..707454fe 100644 --- a/lib/server/peer-limits.c +++ b/lib/server/peer-limits.c @@ -70,10 +70,9 @@ lws_get_or_create_peer(struct lws_vhost *vhost, lws_sockfd_type sockfd) } #endif rlen = sizeof(addr); - if (getpeername(sockfd, (struct sockaddr*)&addr, &rlen)) { - lwsl_notice("%s: getpeername failed\n", __func__); + if (getpeername(sockfd, (struct sockaddr*)&addr, &rlen)) + /* eg, udp doesn't have to have a peer */ return NULL; - } if (af == AF_INET) { struct sockaddr_in *s = (struct sockaddr_in *)&addr; @@ -111,6 +110,7 @@ lws_get_or_create_peer(struct lws_vhost *vhost, lws_sockfd_type sockfd) peer = lws_zalloc(sizeof(*peer), "peer"); if (!peer) { lws_context_unlock(context); /* === */ + lwsl_err("%s: OOM for new peer\n", __func__); return NULL; } diff --git a/lib/server/server.c b/lib/server/server.c index 25d46073..3fa76020 100644 --- a/lib/server/server.c +++ b/lib/server/server.c @@ -2012,11 +2012,7 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, if (type & LWS_ADOPT_SOCKET && !(type & LWS_ADOPT_WS_PARENTIO)) { peer = lws_get_or_create_peer(vh, fd.sockfd); - if (!peer) { - lwsl_err("OOM creating peer\n"); - return NULL; - } - if (context->ip_limit_wsi && + if (peer && context->ip_limit_wsi && peer->count_wsi >= context->ip_limit_wsi) { lwsl_notice("Peer reached wsi limit %d\n", context->ip_limit_wsi); @@ -2092,6 +2088,13 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, lwsl_debug("%s: new wsi %p, sockfd %d\n", __func__, new_wsi, (int)(lws_intptr_t)fd.sockfd); + if (type & LWS_ADOPT_FLAG_UDP) + /* + * these can be >128 bytes, so just alloc for UDP + */ + new_wsi->udp = lws_malloc(sizeof(*new_wsi->udp), + "udp struct"); + if (type & LWS_ADOPT_HTTP) /* the transport is accepted... * give him time to negotiate */