/* * Copyright (C) 2015 Freie Universität Berlin * Copyright (C) 2015 INRIA * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @{ * @file * @brief Providing implementation for POSIX socket wrapper. * @author Martine Lenders * @author Oliver Hahm * @todo */ #include #include #include #include #include "fd.h" #include "mutex.h" #include "net/conn.h" #include "net/ipv4/addr.h" #include "net/ipv6/addr.h" #include "random.h" #include "sys/socket.h" #include "netinet/in.h" #ifdef MODULE_CONN_IP # include "net/conn/ip.h" #endif /* MODULE_CONN_IP */ #ifdef MODULE_CONN_TCP # include "net/conn/tcp.h" #endif /* MODULE_CONN_TCP */ #ifdef MODULE_CONN_UDP # include "net/conn/udp.h" #endif /* MODULE_CONN_UDP */ #define SOCKET_POOL_SIZE (4) /** * @brief Unitfied connection type. */ typedef union { /* is not supposed to be used */ /* cppcheck-suppress unusedStructMember */ int undef; /**< for case that no connection module is present */ #ifdef MODULE_CONN_IP conn_ip_t raw; /**< raw IP connection */ #endif /* MODULE_CONN_IP */ #ifdef MODULE_CONN_TCP conn_tcp_t tcp; /**< TCP connection */ #endif /* MODULE_CONN_TCP */ #ifdef MODULE_CONN_UDP conn_udp_t udp; /**< UDP connection */ #endif /* MODULE_CONN_UDP */ } socket_conn_t; typedef struct { int fd; sa_family_t domain; int type; int protocol; bool bound; socket_conn_t conn; uint16_t src_port; } socket_t; socket_t _pool[SOCKET_POOL_SIZE]; mutex_t _pool_mutex = MUTEX_INIT; const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; static socket_t *_get_free_socket(void) { for (int i = 0; i < SOCKET_POOL_SIZE; i++) { if (_pool[i].domain == AF_UNSPEC) { return &_pool[i]; } } return NULL; } static socket_t *_get_socket(int fd) { for (int i = 0; i < SOCKET_POOL_SIZE; i++) { if (_pool[i].fd == fd) { return &_pool[i]; } } return NULL; } static inline int _choose_ipproto(int type, int protocol) { switch (type) { #ifdef MODULE_CONN_TCP case SOCK_STREAM: if ((protocol == 0) || (protocol == IPPROTO_TCP)) { return protocol; } else { errno = EPROTOTYPE; } break; #endif #ifdef MODULE_CONN_UDP case SOCK_DGRAM: if ((protocol == 0) || (protocol == IPPROTO_UDP)) { return protocol; } else { errno = EPROTOTYPE; } break; #endif #ifdef MODULE_CONN_IP case SOCK_RAW: return protocol; #endif default: (void)protocol; break; } errno = EPROTONOSUPPORT; return -1; } static inline ipv4_addr_t *_in_addr_ptr(struct sockaddr_storage *addr) { return (ipv4_addr_t *)(&((struct sockaddr_in *)addr)->sin_addr); } static inline uint16_t *_in_port_ptr(struct sockaddr_storage *addr) { return &((struct sockaddr_in *)addr)->sin_port; } static inline ipv6_addr_t *_in6_addr_ptr(struct sockaddr_storage *addr) { return (ipv6_addr_t *)(&((struct sockaddr_in6 *)addr)->sin6_addr); } static inline uint16_t *_in6_port_ptr(struct sockaddr_storage *addr) { return &((struct sockaddr_in6 *)addr)->sin6_port; } static inline socklen_t _addr_truncate(struct sockaddr *out, socklen_t out_len, struct sockaddr_storage *in, socklen_t target_size) { out_len = (out_len < target_size) ? out_len : target_size; memcpy(out, in, out_len); return out_len; } static inline int _get_data_from_sockaddr(const struct sockaddr *address, size_t address_len, void **addr, size_t *addr_len, network_uint16_t *port) { switch (address->sa_family) { case AF_INET: if (address_len < sizeof(struct sockaddr_in)) { errno = EINVAL; return -1; } struct sockaddr_in *in_addr = (struct sockaddr_in *)address; *addr = &in_addr->sin_addr; *addr_len = sizeof(ipv4_addr_t); port->u16 = in_addr->sin_port; break; case AF_INET6: if (address_len < sizeof(struct sockaddr_in6)) { errno = EINVAL; return -1; } struct sockaddr_in6 *in6_addr = (struct sockaddr_in6 *)address; *addr = &in6_addr->sin6_addr; *addr_len = sizeof(ipv6_addr_t); port->u16 = in6_addr->sin6_port; break; default: errno = EAFNOSUPPORT; return -1; } return 0; } static int _implicit_bind(socket_t *s, void *addr) { ipv6_addr_t unspec; ipv6_addr_t *best_match; int res; /* TODO: ensure that this port hasn't been used yet */ s->src_port = (uint16_t)random_uint32_range(1LU << 10U, 1LU << 16U); /* find the best matching source address */ if ((best_match = conn_find_best_source(addr)) == NULL) { ipv6_addr_set_unspecified(&unspec); best_match = &unspec; } switch (s->type) { #ifdef MODULE_CONN_TCP case SOCK_STREAM: res = conn_tcp_create(&s->conn.udp, best_match, sizeof(unspec), s->domain, s->src_port); break; #endif #ifdef MODULE_CONN_UDP case SOCK_DGRAM: res = conn_udp_create(&s->conn.udp, best_match, sizeof(unspec), s->domain, s->src_port); break; #endif default: res = -1; break; } if (res < 0) { errno = -res; } else { s->bound = true; } return res; } static int socket_close(int socket) { socket_t *s; int res = 0; if ((unsigned)(socket - 1) > (SOCKET_POOL_SIZE - 1)) { return -1; } mutex_lock(&_pool_mutex); s = &_pool[socket]; if (s->bound) { switch (s->domain) { case AF_INET: case AF_INET6: switch (s->type) { #ifdef MODULE_CONN_UDP case SOCK_DGRAM: conn_udp_close(&s->conn.udp); break; #endif #ifdef MODULE_CONN_IP case SOCK_RAW: conn_ip_close(&s->conn.raw); break; #endif #ifdef MODULE_CONN_TCP case SOCK_STREAM: conn_tcp_close(&s->conn.tcp); break; #endif default: errno = EOPNOTSUPP; res = -1; break; } break; default: res = -1; break; } } s->domain = AF_UNSPEC; s->src_port = 0; mutex_unlock(&_pool_mutex); return res; } static ssize_t socket_read(int socket, void *buf, size_t n) { return recv(socket, buf, n, 0); } static ssize_t socket_write(int socket, const void *buf, size_t n) { return send(socket, buf, n, 0); } int socket(int domain, int type, int protocol) { int res = 0; socket_t *s; mutex_lock(&_pool_mutex); s = _get_free_socket(); if (s == NULL) { errno = ENFILE; mutex_unlock(&_pool_mutex); return -1; } switch (domain) { case AF_INET: case AF_INET6: s->domain = domain; s->type = type; if ((s->protocol = _choose_ipproto(type, protocol)) < 0) { res = -1; } break; default: (void)type; (void)protocol; errno = EAFNOSUPPORT; res = -1; } if (res == 0) { /* TODO: add read and write */ int fd = fd_new(s - _pool, socket_read, socket_write, socket_close); if (fd < 0) { errno = ENFILE; res = -1; } else { s->fd = res = fd; } } s->bound = false; s->src_port = 0; mutex_unlock(&_pool_mutex); return res; } int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len) { socket_t *s, *new_s = NULL; int res = 0; /* May be kept unassigned if no conn module is available */ /* cppcheck-suppress unassignedVariable */ struct sockaddr_storage tmp; void *addr; uint16_t *port; socklen_t tmp_len; mutex_lock(&_pool_mutex); s = _get_socket(socket); if (s == NULL) { mutex_unlock(&_pool_mutex); errno = ENOTSOCK; return -1; } if (!s->bound) { mutex_unlock(&_pool_mutex); errno = EINVAL; return -1; } switch (s->domain) { case AF_INET: addr = _in_addr_ptr(&tmp); port = _in_port_ptr(&tmp); tmp_len = sizeof(struct sockaddr_in); break; case AF_INET6: addr = _in6_addr_ptr(&tmp); port = _in6_port_ptr(&tmp); tmp_len = sizeof(struct sockaddr_in6); break; default: (void)address; (void)address_len; (void)new_s; (void)tmp; (void)addr; (void)port; (void)tmp_len; errno = EPROTO; res = -1; break; } switch (s->type) { #ifdef MODULE_CONN_TCP case SOCK_STREAM: new_s = _get_free_socket(); if (new_s == NULL) { errno = ENFILE; res = -1; break; } if ((res = conn_tcp_accept(&s->conn.tcp, &new_s->conn.tcp)) < 0) { errno = -res; res = -1; break; } else if ((address != NULL) && (address_len != NULL)) { /* TODO: add read and write */ int fd = fd_new(new_s - _pool, NULL, NULL, socket_close); if (fd < 0) { errno = ENFILE; res = -1; break; } else { new_s->fd = res = fd; } new_s->domain = s->domain; new_s->type = s->type; new_s->protocol = s->protocol; tmp.ss_family = s->domain; if ((res = conn_tcp_getpeeraddr(&s->conn.tcp, addr, port)) < 0) { errno = -res; res = -1; break; } *port = htons(*port); /* XXX: sin(6)_port is supposed to be network byte order */ *address_len = _addr_truncate(address, *address_len, &tmp, tmp_len); } break; #endif default: errno = EOPNOTSUPP; res = -1; break; } mutex_unlock(&_pool_mutex); return res; } int bind(int socket, const struct sockaddr *address, socklen_t address_len) { socket_t *s; int res = 0; void *addr; size_t addr_len; network_uint16_t port = { 0 }; mutex_lock(&_pool_mutex); s = _get_socket(socket); mutex_unlock(&_pool_mutex); if (s == NULL) { errno = ENOTSOCK; return -1; } if (address->sa_family != s->domain) { errno = EAFNOSUPPORT; return -1; } if (_get_data_from_sockaddr(address, address_len, &addr, &addr_len, &port) < 0) { return -1; } switch (s->type) { #ifdef MODULE_CONN_IP case SOCK_RAW: (void)port; if ((res = conn_ip_create(&s->conn.raw, addr, addr_len, s->domain, s->protocol)) < 0) { errno = -res; return -1; } break; #endif #ifdef MODULE_CONN_TCP case SOCK_STREAM: if ((res = conn_tcp_create(&s->conn.tcp, addr, addr_len, s->domain, byteorder_ntohs(port))) < 0) { errno = -res; return -1; } break; #endif #ifdef MODULE_CONN_UDP case SOCK_DGRAM: if ((res = conn_udp_create(&s->conn.udp, addr, addr_len, s->domain, byteorder_ntohs(port))) < 0) { errno = -res; return -1; } break; #endif default: (void)addr; (void)addr_len; (void)port; (void)res; errno = EOPNOTSUPP; return -1; } s->src_port = byteorder_ntohs(port); s->bound = true; return 0; } int connect(int socket, const struct sockaddr *address, socklen_t address_len) { socket_t *s; int res = 0; void *addr; size_t addr_len; network_uint16_t port; mutex_lock(&_pool_mutex); s = _get_socket(socket); mutex_unlock(&_pool_mutex); if (s == NULL) { errno = ENOTSOCK; return -1; } if (address->sa_family != s->domain) { errno = EAFNOSUPPORT; return -1; } if (_get_data_from_sockaddr(address, address_len, &addr, &addr_len, &port) < 0) { return -1; } switch (s->type) { #ifdef MODULE_CONN_TCP case SOCK_STREAM: /* "If the socket has not already been bound to a local address, * connect() shall bind it to an address which, unless the socket's * address family is AF_UNIX, is an unused local address." (see * http://pubs.opengroup.org/onlinepubs/009695399/functions/connect.html) */ if (!s->bound) { if ((res = _implicit_bind(s, addr)) < 0) { return res; } } if ((res = conn_tcp_connect(&s->conn.tcp, addr, addr_len, byteorder_ntohs(port))) < 0) { errno = -res; return -1; } break; #endif default: (void)res; errno = EPROTOTYPE; return -1; } return 0; } int getpeername(int socket, struct sockaddr *__restrict address, socklen_t *__restrict address_len) { socket_t *s; int res = 0; /* May be kept unassigned if no conn module is available */ /* cppcheck-suppress unassignedVariable */ struct sockaddr_storage tmp; void *addr; uint16_t *port; socklen_t tmp_len; mutex_lock(&_pool_mutex); s = _get_socket(socket); mutex_unlock(&_pool_mutex); if (s == NULL) { errno = ENOTSOCK; return -1; } switch (s->domain) { case AF_INET: addr = _in_addr_ptr(&tmp); port = _in_port_ptr(&tmp); tmp_len = sizeof(struct sockaddr_in); break; case AF_INET6: addr = _in6_addr_ptr(&tmp); port = _in6_port_ptr(&tmp); tmp_len = sizeof(struct sockaddr_in6); break; default: (void)address; (void)address_len; (void)tmp; (void)addr; (void)port; (void)tmp_len; (void)res; errno = EBADF; return -1; } if (*address_len != tmp_len) { errno = EINVAL; return -1; } switch (s->type) { #ifdef MODULE_CONN_TCP case SOCK_STREAM: if ((res = conn_tcp_getpeeraddr(&s->conn.tcp, addr, port)) < 0) { errno = -res; return -1; } break; #endif default: errno = ENOTCONN; return -1; } tmp.ss_family = s->domain; *port = htons(*port); /* XXX: sin(6)_port is supposed to be network byte order */ *address_len = _addr_truncate(address, *address_len, &tmp, tmp_len); return 0; } int getsockname(int socket, struct sockaddr *__restrict address, socklen_t *__restrict address_len) { socket_t *s; int res = 0; /* May be kept unassigned if no conn module is available */ /* cppcheck-suppress unassignedVariable */ struct sockaddr_storage tmp; void *addr; uint16_t *port; socklen_t tmp_len; mutex_lock(&_pool_mutex); s = _get_socket(socket); mutex_unlock(&_pool_mutex); if (s == NULL) { errno = ENOTSOCK; return -1; } if (!s->bound) { memset(address, 0, *address_len); return 0; } switch (s->domain) { case AF_INET: addr = _in_addr_ptr(&tmp); port = _in_port_ptr(&tmp); tmp_len = sizeof(struct sockaddr_in); break; case AF_INET6: addr = _in6_addr_ptr(&tmp); port = _in6_port_ptr(&tmp); tmp_len = sizeof(struct sockaddr_in6); break; default: (void)address; (void)address_len; (void)tmp; (void)addr; (void)port; (void)tmp_len; (void)res; errno = EBADF; return -1; } if (*address_len != tmp_len) { errno = EINVAL; return -1; } switch (s->type) { #ifdef MODULE_CONN_UDP case SOCK_DGRAM: if ((res = conn_udp_getlocaladdr(&s->conn.udp, addr, port)) < 0) { errno = -res; return -1; } break; #endif #ifdef MODULE_CONN_IP case SOCK_RAW: if ((res = conn_ip_getlocaladdr(&s->conn.raw, addr)) < 0) { errno = -res; return -1; } break; #endif #ifdef MODULE_CONN_TCP case SOCK_STREAM: if ((res = conn_tcp_getlocaladdr(&s->conn.tcp, addr, port)) < 0) { errno = -res; return -1; } break; #endif default: errno = EOPNOTSUPP; return -1; } tmp.ss_family = AF_INET; *port = htons(*port); /* XXX: sin(6)_port is supposed to be network byte order */ *address_len = _addr_truncate(address, *address_len, &tmp, tmp_len); return 0; } int listen(int socket, int backlog) { socket_t *s; int res = 0; mutex_lock(&_pool_mutex); s = _get_socket(socket); mutex_unlock(&_pool_mutex); if (!s->bound) { errno = EINVAL; return -1; } switch (s->domain) { case AF_INET: case AF_INET6: switch (s->type) { #ifdef MODULE_CONN_TCP case SOCK_STREAM: if ((res = conn_tcp_listen(&s->conn.tcp, backlog)) < 0) { errno = -res; return -1; } break; #endif default: errno = EOPNOTSUPP; return -1; } break; default: (void)backlog; (void)res; errno = EAFNOSUPPORT; return -1; } return 0; } ssize_t recv(int socket, void *buffer, size_t length, int flags) { return recvfrom(socket, buffer, length, flags, NULL, NULL); } ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len) { socket_t *s; int res = 0; /* May be kept unassigned if no conn module is available */ /* cppcheck-suppress unassignedVariable */ struct sockaddr_storage tmp; void *addr; size_t addr_len; uint16_t *port; socklen_t tmp_len; (void)flags; mutex_lock(&_pool_mutex); s = _get_socket(socket); mutex_unlock(&_pool_mutex); if (s == NULL) { errno = ENOTSOCK; return -1; } if (!s->bound) { errno = EINVAL; return -1; } memset(&tmp, 0, sizeof(struct sockaddr_storage)); switch (s->domain) { case AF_INET: addr = _in_addr_ptr(&tmp); port = _in_port_ptr(&tmp); addr_len = sizeof(ipv4_addr_t); tmp_len = sizeof(struct sockaddr_in); break; case AF_INET6: addr = _in6_addr_ptr(&tmp); port = _in6_port_ptr(&tmp); addr_len = sizeof(ipv6_addr_t); tmp_len = sizeof(struct sockaddr_in6); break; default: (void)buffer; (void)length; (void)address; (void)address_len; (void)tmp; (void)addr; (void)port; (void)tmp_len; errno = EAFNOSUPPORT; return -1; } switch (s->type) { #ifdef MODULE_CONN_UDP case SOCK_DGRAM: if ((res = conn_udp_recvfrom(&s->conn.udp, buffer, length, addr, &addr_len, port)) < 0) { errno = -res; return -1; } break; #endif #ifdef MODULE_CONN_IP case SOCK_RAW: if ((res = conn_ip_recvfrom(&s->conn.raw, buffer, length, addr, &addr_len)) < 0) { errno = -res; return -1; } break; #endif #ifdef MODULE_CONN_TCP case SOCK_STREAM: if ((res = conn_tcp_recv(&s->conn.tcp, buffer, length)) < 0) { errno = -res; return -1; } if ((res = conn_tcp_getpeeraddr(&s->conn.tcp, addr, port)) < 0) { errno = -res; return -1; } break; #endif default: (void)addr_len; errno = EOPNOTSUPP; return -1; } if ((address != NULL) && (address_len != NULL)) { tmp.ss_family = s->domain; *port = htons(*port); /* XXX: sin(6)_port is supposed to be network byte order */ *address_len = _addr_truncate(address, *address_len, &tmp, tmp_len); } return res; } ssize_t send(int socket, const void *buffer, size_t length, int flags) { return sendto(socket, buffer, length, flags, NULL, 0); } ssize_t sendto(int socket, const void *buffer, size_t length, int flags, const struct sockaddr *address, socklen_t address_len) { socket_t *s; int res = 0; void *addr = NULL; size_t addr_len = 0; network_uint16_t port; port.u16 = 0; (void)flags; mutex_lock(&_pool_mutex); s = _get_socket(socket); mutex_unlock(&_pool_mutex); if (s == NULL) { errno = ENOTSOCK; return -1; } if (address != NULL) { if (address->sa_family != s->domain) { errno = EAFNOSUPPORT; return -1; } if (_get_data_from_sockaddr(address, address_len, &addr, &addr_len, &port) < 0) { return -1; } } switch (s->type) { #ifdef MODULE_CONN_IP case SOCK_RAW: if ((address != NULL) && (s->bound)) { uint8_t src_addr[sizeof(ipv6_addr_t)]; size_t src_len; int res = conn_ip_getlocaladdr(&s->conn.raw, src_addr); if (res < 0) { errno = ENOTSOCK; /* Something seems to be wrong with the socket */ return -1; } src_len = (size_t)res; /* cppcheck bug? res is read below in l824 */ /* cppcheck-suppress unreadVariable */ res = conn_ip_sendto(buffer, length, src_addr, src_len, addr, addr_len, s->domain, s->protocol); } else if (address != NULL) { res = conn_ip_sendto(buffer, length, NULL, 0, addr, addr_len, s->domain, s->protocol); } else { errno = ENOTCONN; return -1; } if (res < 0) { errno = -res; return -1; } break; #endif #ifdef MODULE_CONN_TCP case SOCK_STREAM: if (!s->bound) { errno = ENOTCONN; return -1; } if (address != NULL) { errno = EISCONN; return -1; } if ((res = conn_tcp_send(&s->conn.tcp, buffer, length)) < 0) { errno = -res; return -1; } break; #endif #ifdef MODULE_CONN_UDP case SOCK_DGRAM: if ((address != NULL) && (s->bound)) { uint8_t src_addr[sizeof(ipv6_addr_t)]; size_t src_len; uint16_t sport; int res = conn_udp_getlocaladdr(&s->conn.udp, src_addr, &sport); if (res < 0) { errno = ENOTSOCK; /* Something seems to be wrong with the socket */ return -1; } src_len = (size_t)res; /* cppcheck bug? res is read below in l824 */ /* cppcheck-suppress unreadVariable */ res = conn_udp_sendto(buffer, length, src_addr, src_len, addr, addr_len, s->domain, sport, byteorder_ntohs(port)); } else if (address != NULL) { if ((res = _implicit_bind(s, addr)) < 0) { return res; } res = conn_udp_sendto(buffer, length, NULL, 0, addr, addr_len, s->domain, s->src_port, byteorder_ntohs(port)); } else { errno = ENOTCONN; return -1; } if (res < 0) { errno = -res; return -1; } break; #endif default: (void)buffer; (void)length; errno = EOPNOTSUPP; return -1; } return res; } /** * @} */