You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

954 lines
26 KiB

/*
* 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 <mlenders@inf.fu-berlin.de>
* @author Oliver Hahm <oliver.hahm@inria.fr>
* @todo
*/
#include <arpa/inet.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#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;
}
/**
* @}
*/