diff --git a/include/net/net_context.h b/include/net/net_context.h index e0d1c7cdf6ecd..d8ab952f22ceb 100644 --- a/include/net/net_context.h +++ b/include/net/net_context.h @@ -176,6 +176,13 @@ struct net_conn_handle; * anyway. This saves 12 bytes / context in IPv6. */ struct net_context { + /** User data. + * + * First member of the structure to let users either have user data + * associated with a context, or put contexts into a FIFO. + */ + void *user_data; + /** Reference count */ atomic_t refcount; @@ -206,10 +213,6 @@ struct net_context { */ net_context_connect_cb_t connect_cb; - /** User data. - */ - void *user_data; - #if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) /** Get TX net_buf pool for this context. */ @@ -237,6 +240,14 @@ struct net_context { /** TCP connection information */ struct net_tcp *tcp; #endif /* CONFIG_NET_TCP */ + +#if defined(CONFIG_NET_SOCKETS) + /** Per-socket packet or connection queues */ + union { + struct k_fifo recv_q; + struct k_fifo accept_q; + }; +#endif /* CONFIG_NET_SOCKETS */ }; static inline bool net_context_is_used(struct net_context *context) diff --git a/include/net/net_pkt.h b/include/net/net_pkt.h index 3d38349f06ab9..da4fa6b6a214b 100644 --- a/include/net/net_pkt.h +++ b/include/net/net_pkt.h @@ -71,7 +71,9 @@ struct net_pkt { sys_snode_t sent_list; #endif - u8_t sent : 1; /* Is this sent or not + u8_t sent_or_eof: 1; /* For outgoing packet: is this sent or not + * For incoming packet of a socket: last + * packet before EOF * Used only if defined(CONFIG_NET_TCP) */ u8_t forwarding : 1; /* Are we forwarding this pkt @@ -188,13 +190,25 @@ static inline void net_pkt_set_next_hdr(struct net_pkt *pkt, u8_t *hdr) #if defined(CONFIG_NET_TCP) static inline u8_t net_pkt_sent(struct net_pkt *pkt) { - return pkt->sent; + return pkt->sent_or_eof; } static inline void net_pkt_set_sent(struct net_pkt *pkt, bool sent) { - pkt->sent = sent; + pkt->sent_or_eof = sent; } + +#if defined(CONFIG_NET_SOCKETS) +static inline u8_t net_pkt_eof(struct net_pkt *pkt) +{ + return pkt->sent_or_eof; +} + +static inline void net_pkt_set_eof(struct net_pkt *pkt, bool eof) +{ + pkt->sent_or_eof = eof; +} +#endif #endif #if defined(CONFIG_NET_ROUTE) diff --git a/include/net/socket.h b/include/net/socket.h new file mode 100644 index 0000000000000..961e086d98342 --- /dev/null +++ b/include/net/socket.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __NET_SOCKET_H +#define __NET_SOCKET_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int zsock_socket(int family, int type, int proto); +int zsock_close(int sock); +int zsock_bind(int sock, const struct sockaddr *addr, socklen_t addrlen); +int zsock_connect(int sock, const struct sockaddr *addr, socklen_t addrlen); +int zsock_listen(int sock, int backlog); +int zsock_accept(int sock, struct sockaddr *addr, socklen_t *addrlen); +ssize_t zsock_send(int sock, const void *buf, size_t len, int flags); +ssize_t zsock_recv(int sock, void *buf, size_t max_len, int flags); + +#if defined(CONFIG_NET_SOCKETS_POSIX_NAMES) +#define socket zsock_socket +#define close zsock_close +#define bind zsock_bind +#define connect zsock_connect +#define listen zsock_listen +#define accept zsock_accept +#define send zsock_send +#define recv zsock_recv + +#define inet_ntop net_addr_ntop +#define inet_pton net_addr_pton +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __NET_SOCKET_H */ diff --git a/samples/net/socket_echo/Makefile b/samples/net/socket_echo/Makefile new file mode 100644 index 0000000000000..7f9b4a97a88db --- /dev/null +++ b/samples/net/socket_echo/Makefile @@ -0,0 +1,18 @@ +# Makefile - simple socket-based echo server + +# +# Copyright (c) 2017 Linaro Limited +# +# SPDX-License-Identifier: Apache-2.0 +# + +BOARD ?= qemu_x86 +CONF_FILE ?= prj_$(BOARD).conf + +include $(ZEPHYR_BASE)/Makefile.inc + +ifeq ($(CONFIG_NET_L2_BLUETOOTH), y) + QEMU_EXTRA_FLAGS = -serial unix:/tmp/bt-server-bredr +else + include $(ZEPHYR_BASE)/samples/net/common/Makefile.ipstack +endif diff --git a/samples/net/socket_echo/prj_qemu_x86.conf b/samples/net/socket_echo/prj_qemu_x86.conf new file mode 100644 index 0000000000000..2435a9e405842 --- /dev/null +++ b/samples/net/socket_echo/prj_qemu_x86.conf @@ -0,0 +1,26 @@ +# General config +CONFIG_NEWLIB_LIBC=y + +# Networking config +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_IPV6=n +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_SOCKETS_POSIX_NAMES=y + +# Network driver config +CONFIG_NET_SLIP_TAP=y +CONFIG_TEST_RANDOM_GENERATOR=y + +# Without CONFIG_NET_BUF_LOG printf() doesn't work +CONFIG_NET_BUF_LOG=y + +# Network address config +CONFIG_NET_APP_SETTINGS=y +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_APP_PEER_IPV4_ADDR="192.0.2.2" + +# Network debug config +#CONFIG_NET_DEBUG_SOCKETS=y +CONFIG_SYS_LOG_NET_LEVEL=2 diff --git a/samples/net/socket_echo/src/Makefile b/samples/net/socket_echo/src/Makefile new file mode 100644 index 0000000000000..073377e184fb7 --- /dev/null +++ b/samples/net/socket_echo/src/Makefile @@ -0,0 +1,3 @@ +include $(ZEPHYR_BASE)/samples/net/common/Makefile.common + +obj-y += socket_echo.o diff --git a/samples/net/socket_echo/src/socket_echo.c b/samples/net/socket_echo/src/socket_echo.c new file mode 100644 index 0000000000000..c9fa4c8b57290 --- /dev/null +++ b/samples/net/socket_echo/src/socket_echo.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#ifndef __ZEPHYR__ + +#include +#include +#include +#include + +#define init_net() + +#else + +#include +#include +#include + +void init_net(void) +{ + int ret = net_sample_app_init("socket_echo", NET_SAMPLE_NEED_IPV4, + K_SECONDS(3)); + + if (ret < 0) { + printf("Application init failed\n"); + k_panic(); + } +} + +#endif + +int main(void) +{ + int serv; + struct sockaddr_in bind_addr; + + init_net(); + + serv = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + bind_addr.sin_family = AF_INET; + bind_addr.sin_addr.s_addr = htonl(INADDR_ANY); + bind_addr.sin_port = htons(4242); + bind(serv, (struct sockaddr *)&bind_addr, sizeof(bind_addr)); + + listen(serv, 5); + + while (1) { + struct sockaddr_in client_addr; + socklen_t client_addr_len = sizeof(client_addr); + char addr_str[32]; + int client = accept(serv, (struct sockaddr *)&client_addr, + &client_addr_len); + inet_ntop(client_addr.sin_family, &client_addr.sin_addr, + addr_str, sizeof(addr_str)); + printf("Connection from %s\n", addr_str); + + while (1) { + char buf[128]; + int len = recv(client, buf, sizeof(buf), 0); + + if (len == 0) { + break; + } + send(client, buf, len, 0); + } + + close(client); + printf("Connection from %s closed\n", addr_str); + } +} diff --git a/subsys/net/lib/Kbuild b/subsys/net/lib/Kbuild index 0cec56ccde4a3..625b341412599 100644 --- a/subsys/net/lib/Kbuild +++ b/subsys/net/lib/Kbuild @@ -1,3 +1,4 @@ +obj-$(CONFIG_NET_SOCKETS) += sockets/ obj-$(CONFIG_ZOAP) += zoap/ obj-$(CONFIG_DNS_RESOLVER) += dns/ obj-$(CONFIG_MQTT_LIB) += mqtt/ diff --git a/subsys/net/lib/Kconfig b/subsys/net/lib/Kconfig index c3ea03d615c26..cf5942dd8c299 100644 --- a/subsys/net/lib/Kconfig +++ b/subsys/net/lib/Kconfig @@ -6,6 +6,8 @@ menu "Network Protocols" +source "subsys/net/lib/sockets/Kconfig" + source "subsys/net/lib/zoap/Kconfig" source "subsys/net/lib/dns/Kconfig" diff --git a/subsys/net/lib/Makefile b/subsys/net/lib/Makefile index 0625b848ec9fe..ae191af75c03d 100644 --- a/subsys/net/lib/Makefile +++ b/subsys/net/lib/Makefile @@ -1,3 +1,7 @@ +ifdef CONFIG_NET_SOCKETS +include $(srctree)/subsys/net/lib/sockets/Makefile +endif + ifdef CONFIG_ZOAP include $(srctree)/subsys/net/lib/zoap/Makefile endif diff --git a/subsys/net/lib/sockets/Kconfig b/subsys/net/lib/sockets/Kconfig new file mode 100644 index 0000000000000..2fae525fe3760 --- /dev/null +++ b/subsys/net/lib/sockets/Kconfig @@ -0,0 +1,35 @@ +# Kconfig - BSD Sockets like API + +# +# Copyright (c) 2017 Linaro Limited. +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig NET_SOCKETS + bool "BSD Sockets like API" + default n + help + Provide BSD Sockets like API on top of native Zephyr networking API. + +if NET_SOCKETS + +config NET_SOCKETS_POSIX_NAMES + bool "Standard POSIX names for Sockets API" + default n + help + By default, Sockets API function are prefixed with "zsock_" to avoid + namespacing issues. If this option is enabled, they will be provided + with standard POSIX names like socket(), recv(), and close(), to help + with porting existing code. Note that close() may require a special + attention, as in POSIX it closes any file descriptor, while with this + option enaled, it will still apply only to sockets. + +config NET_DEBUG_SOCKETS + bool "Debug BSD Sockets like API calls" + default n + help + Enables logging for sockets code. (Logging level is defined by + SYS_LOG_NET_LEVEL setting). + +endif # NET_SOCKETS diff --git a/subsys/net/lib/sockets/Makefile b/subsys/net/lib/sockets/Makefile new file mode 100644 index 0000000000000..f1ec5711fe00a --- /dev/null +++ b/subsys/net/lib/sockets/Makefile @@ -0,0 +1,3 @@ +ccflags-y += -I$(srctree)/subsys/net/lib/sockets + +obj-$(CONFIG_NET_SOCKETS) += sockets.o diff --git a/subsys/net/lib/sockets/sockets.c b/subsys/net/lib/sockets/sockets.c new file mode 100644 index 0000000000000..ee0c93e7828e4 --- /dev/null +++ b/subsys/net/lib/sockets/sockets.c @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2017 Linaro Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if defined(CONFIG_NET_DEBUG_SOCKETS) +#define SYS_LOG_DOMAIN "net/sock" +#define NET_LOG_ENABLED 1 +#endif + +#include +#include +#include +#include "sockets_int.h" + +#define SET_ERRNO(x) \ + { int _err = x; if (_err < 0) { errno = -_err; return -1; } } + +#define sock_is_eof(ctx) ((ctx)->user_data != NULL) +#define sock_set_eof(ctx) { (ctx)->user_data = INT_TO_POINTER(1); } + +static inline void _k_fifo_wait_non_empty(struct k_fifo *fifo, int32_t timeout) +{ + struct k_poll_event events[] = { + K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_FIFO_DATA_AVAILABLE, + K_POLL_MODE_NOTIFY_ONLY, fifo), + }; + + k_poll(events, ARRAY_SIZE(events), timeout); +} + +static void zsock_flush_queue(struct net_context *ctx) +{ + bool is_listen = net_context_get_state(ctx) == NET_CONTEXT_LISTENING; + void *p; + + /* recv_q and accept_q are shared via a union */ + while ((p = k_fifo_get(&ctx->recv_q, K_NO_WAIT)) != NULL) { + if (is_listen) { + NET_DBG("discarding ctx %p", p); + net_context_put(p); + } else { + NET_DBG("discarding pkt %p", p); + net_pkt_unref(p); + } + } +} + +int zsock_socket(int family, int type, int proto) +{ + struct net_context *ctx; + + SET_ERRNO(net_context_get(family, type, proto, &ctx)); + /* recv_q and accept_q are in union */ + k_fifo_init(&ctx->recv_q); + + /* TODO: Ensure non-negative */ + return POINTER_TO_INT(ctx); +} + +int zsock_close(int sock) +{ + struct net_context *ctx = INT_TO_POINTER(sock); + + /* Reset callbacks to avoid any race conditions while + * flushing queues. + */ + net_context_accept(ctx, NULL, K_NO_WAIT, NULL); + net_context_recv(ctx, NULL, K_NO_WAIT, NULL); + + zsock_flush_queue(ctx); + + SET_ERRNO(net_context_put(ctx)); + return 0; +} + +static void zsock_accepted_cb(struct net_context *new_ctx, + struct sockaddr *addr, socklen_t addrlen, + int status, void *user_data) { + struct net_context *parent = user_data; + + NET_DBG("parent=%p, ctx=%p, st=%d", parent, new_ctx, status); + + k_fifo_put(&parent->accept_q, new_ctx); +} + +static void zsock_received_cb(struct net_context *ctx, struct net_pkt *pkt, + int status, void *user_data) { + unsigned int header_len; + + NET_DBG("ctx=%p, pkt=%p, st=%d, user_data=%p", ctx, pkt, status, + user_data); + + /* if pkt == NULL, EOF */ + if (pkt == NULL) { + struct net_pkt *last_pkt = _k_fifo_peek_tail(&ctx->recv_q); + + if (last_pkt == NULL) { + /* If there're no packets in the queue, recv() may + * be blocked waiting on it to become non-empty, + * so cancel that wait. + */ + sock_set_eof(ctx); + k_fifo_cancel_wait(&ctx->recv_q); + NET_DBG("Marked socket %p as peer-closed\n", ctx); + } else { + net_pkt_set_eof(last_pkt, true); + NET_DBG("Set EOF flag on pkt %p\n", ctx); + } + return; + } + + /* Normal packet */ + net_pkt_set_eof(pkt, false); + + /* We don't care about packet header, so get rid of it asap */ + header_len = net_pkt_appdata(pkt) - pkt->frags->data; + net_buf_pull(pkt->frags, header_len); + + k_fifo_put(&ctx->recv_q, pkt); +} + +int zsock_bind(int sock, const struct sockaddr *addr, socklen_t addrlen) +{ + struct net_context *ctx = INT_TO_POINTER(sock); + + SET_ERRNO(net_context_bind(ctx, addr, addrlen)); + /* For DGRAM socket, we expect to receive packets after call to + * bind(), but for STREAM socket, next expected operation is + * listen(), which doesn't work if recv callback is set. + */ + if (net_context_get_type(ctx) == SOCK_DGRAM) { + SET_ERRNO(net_context_recv(ctx, zsock_received_cb, K_NO_WAIT, + NULL)); + } + + return 0; +} + +int zsock_connect(int sock, const struct sockaddr *addr, socklen_t addrlen) +{ + struct net_context *ctx = INT_TO_POINTER(sock); + + SET_ERRNO(net_context_connect(ctx, addr, addrlen, NULL, K_FOREVER, + NULL)); + SET_ERRNO(net_context_recv(ctx, zsock_received_cb, K_NO_WAIT, NULL)); + + return 0; +} + +int zsock_listen(int sock, int backlog) +{ + struct net_context *ctx = INT_TO_POINTER(sock); + + SET_ERRNO(net_context_listen(ctx, backlog)); + SET_ERRNO(net_context_accept(ctx, zsock_accepted_cb, K_NO_WAIT, ctx)); + + return 0; +} + +int zsock_accept(int sock, struct sockaddr *addr, socklen_t *addrlen) +{ + struct net_context *parent = INT_TO_POINTER(sock); + + struct net_context *ctx = k_fifo_get(&parent->accept_q, K_FOREVER); + + SET_ERRNO(net_context_recv(ctx, zsock_received_cb, K_NO_WAIT, NULL)); + + if (addr != NULL && addrlen != NULL) { + int len = min(*addrlen, sizeof(ctx->remote)); + + memcpy(addr, &ctx->remote, len); + *addrlen = sizeof(ctx->remote); + } + + /* TODO: Ensure non-negative */ + return POINTER_TO_INT(ctx); +} + +ssize_t zsock_send(int sock, const void *buf, size_t len, int flags) +{ + (void)flags; + int err; + struct net_context *ctx = INT_TO_POINTER(sock); + struct net_pkt *send_pkt = net_pkt_get_tx(ctx, K_FOREVER); + size_t max_len = net_if_get_mtu(net_context_get_iface(ctx)); + + /* Make sure we don't send more data in one packet than + * MTU allows. Optimize for number of branches in the code. + */ + max_len -= NET_IPV4TCPH_LEN; + if (net_context_get_family(ctx) != AF_INET) { + max_len -= NET_IPV6TCPH_LEN - NET_IPV4TCPH_LEN; + } + + if (len > max_len) { + len = max_len; + } + + len = net_pkt_append(send_pkt, len, buf, K_FOREVER); + err = net_context_send(send_pkt, /*cb*/NULL, K_FOREVER, NULL, NULL); + if (err < 0) { + net_pkt_unref(send_pkt); + errno = -err; + return -1; + } + + return len; +} + +ssize_t zsock_recv(int sock, void *buf, size_t max_len, int flags) +{ + (void)flags; + struct net_context *ctx = INT_TO_POINTER(sock); + enum net_sock_type sock_type = net_context_get_type(ctx); + size_t recv_len = 0; + + if (sock_type == SOCK_DGRAM) { + __ASSERT(0, "DGRAM is not yet handled"); + } else if (sock_type == SOCK_STREAM) { + do { + struct net_pkt *pkt; + struct net_buf *frag; + u32_t frag_len; + + if (sock_is_eof(ctx)) { + return 0; + } + + _k_fifo_wait_non_empty(&ctx->recv_q, K_FOREVER); + pkt = _k_fifo_peek_head(&ctx->recv_q); + if (pkt == NULL) { + /* An expected reason is that wait was + * cancelled due to connection closure by peer. + */ + NET_DBG("NULL return from fifo"); + continue; + } + + frag = pkt->frags; + __ASSERT(frag != NULL, + "net_pkt has empty fragments on start!"); + frag_len = frag->len; + recv_len = frag_len; + if (recv_len > max_len) { + recv_len = max_len; + } + + /* Actually copy data to application buffer */ + memcpy(buf, frag->data, recv_len); + + if (recv_len != frag_len) { + net_buf_pull(frag, recv_len); + } else { + frag = net_pkt_frag_del(pkt, NULL, frag); + if (frag == NULL) { + /* Finished processing head pkt in + * the fifo. Drop it from there. + */ + k_fifo_get(&ctx->recv_q, K_NO_WAIT); + if (net_pkt_eof(pkt)) { + sock_set_eof(ctx); + } + net_pkt_unref(pkt); + } + } + } while (recv_len == 0); + } else { + __ASSERT(0, "Unknown socket type"); + } + + return recv_len; +} diff --git a/subsys/net/lib/sockets/sockets_int.h b/subsys/net/lib/sockets/sockets_int.h new file mode 100644 index 0000000000000..f9358367bf9f0 --- /dev/null +++ b/subsys/net/lib/sockets/sockets_int.h @@ -0,0 +1,11 @@ +#include + +static inline void *_k_fifo_peek_head(struct k_fifo *fifo) +{ + return sys_slist_peek_head(&fifo->_queue.data_q); +} + +static inline void *_k_fifo_peek_tail(struct k_fifo *fifo) +{ + return sys_slist_peek_tail(&fifo->_queue.data_q); +}