Goto sanos source index
//
// udpsock.c
//
// UDP socket interface
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <net/net.h>
static err_t recv_udp(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, unsigned short port) {
struct socket *s = arg;
struct sockreq *req = s->waithead;
struct sockaddr_in *sin;
int rc;
if (req) {
rc = write_iovec(req->msg->msg_iov, req->msg->msg_iovlen, p->payload, p->len);
if (rc < p->len) rc = -EMSGSIZE;
if (req->msg->msg_name) {
if (req->msg->msg_namelen < sizeof(struct sockaddr_in)) {
rc = -EFAULT;
} else {
sin = (struct sockaddr_in *) req->msg->msg_name;
sin->sin_family = AF_INET;
sin->sin_port = htons(port);
sin->sin_addr.s_addr = addr->addr;
}
}
req->msg->msg_namelen = sizeof(struct sockaddr_in);
pbuf_free(p);
release_socket_request(req, rc);
} else {
if (p->next) {
kprintf("recv_udp: fragmented pbuf not supported\n");
return -EINVAL;
}
if (s->udp.recvtail) {
pbuf_chain(s->udp.recvtail, p);
s->udp.recvtail = p;
} else {
s->udp.recvhead = p;
s->udp.recvtail = p;
}
set_io_event(&s->iob, IOEVT_READ);
}
return 0;
}
static int udpsock_accept(struct socket *s, struct sockaddr *addr, int *addrlen, struct socket **retval) {
return -EINVAL;
}
static int udpsock_bind(struct socket *s, struct sockaddr *name, int namelen) {
int rc;
struct sockaddr_in *sin;
if (!name) return -EFAULT;
if (namelen < sizeof(struct sockaddr_in)) return -EFAULT;
sin = (struct sockaddr_in *) name;
if (sin->sin_family != AF_INET && sin->sin_family != AF_UNSPEC) return -EAFNOSUPPORT;
if (!s->udp.pcb) {
s->udp.pcb = udp_new();
if (!s->udp.pcb) return -ENOMEM;
if (s->flags & SOCK_BCAST) s->udp.pcb->flags |= UDP_FLAGS_BROADCAST;
udp_recv(s->udp.pcb, recv_udp, s);
}
rc = udp_bind(s->udp.pcb, (struct ip_addr *) &sin->sin_addr, ntohs(sin->sin_port));
if (rc < 0) return rc;
s->state = SOCKSTATE_BOUND;
return 0;
}
static int udpsock_close(struct socket *s) {
struct sockreq *req;
struct sockreq *next;
s->state = SOCKSTATE_CLOSED;
req = s->waithead;
while (req) {
next = req->next;
release_socket_request(req, -EABORT);
req = next;
}
if (s->udp.pcb) {
s->udp.pcb->recv_arg = NULL;
udp_remove(s->udp.pcb);
}
if (s->udp.recvhead) pbuf_free(s->udp.recvhead);
return 0;
}
static int udpsock_connect(struct socket *s, struct sockaddr *name, int namelen) {
int rc;
struct sockaddr_in *sin;
if (!name) return -EFAULT;
if (namelen < sizeof(struct sockaddr_in)) return -EFAULT;
sin = (struct sockaddr_in *) name;
if (sin->sin_family != AF_INET && sin->sin_family != AF_UNSPEC) return -EAFNOSUPPORT;
if (s->state == SOCKSTATE_CLOSED) return -EINVAL;
if (!s->udp.pcb) {
s->udp.pcb = udp_new();
if (!s->udp.pcb) return -ENOMEM;
if (s->flags & SOCK_BCAST) s->udp.pcb->flags |= UDP_FLAGS_BROADCAST;
udp_recv(s->udp.pcb, recv_udp, s);
}
rc = udp_connect(s->udp.pcb, (struct ip_addr *) &sin->sin_addr, ntohs(sin->sin_port));
if (rc < 0) return rc;
s->state = SOCKSTATE_CONNECTED;
return 0;
}
static int udpsock_getpeername(struct socket *s, struct sockaddr *name, int *namelen) {
struct sockaddr_in *sin;
if (!namelen) return -EFAULT;
if (*namelen < sizeof(struct sockaddr_in)) return -EFAULT;
if (s->state != SOCKSTATE_CONNECTED) return -EINVAL;
sin = (struct sockaddr_in *) name;
sin->sin_family = AF_INET;
sin->sin_port = htons(s->udp.pcb->remote_port);
sin->sin_addr.s_addr = s->udp.pcb->remote_ip.addr;
*namelen = sizeof(struct sockaddr_in);
return 0;
}
static int udpsock_getsockname(struct socket *s, struct sockaddr *name, int *namelen) {
struct sockaddr_in *sin;
if (!namelen) return -EFAULT;
if (*namelen < sizeof(struct sockaddr_in)) return -EFAULT;
if (s->state != SOCKSTATE_BOUND && s->state != SOCKSTATE_CONNECTED) return -EINVAL;
sin = (struct sockaddr_in *) name;
sin->sin_family = AF_INET;
sin->sin_port = htons(s->udp.pcb->local_port);
sin->sin_addr.s_addr = s->udp.pcb->local_ip.addr;
*namelen = sizeof(struct sockaddr_in);
return 0;
}
static int udpsock_getsockopt(struct socket *s, int level, int optname, void *optval, int *optlen) {
return -ENOSYS;
}
static int udpsock_ioctl(struct socket *s, int cmd, void *data, size_t size) {
switch (cmd) {
case FIONBIO:
if (!data || size != 4) return -EFAULT;
if (*(int *) data) {
s->flags |= SOCK_NBIO;
} else {
s->flags &= ~SOCK_NBIO;
}
break;
default:
return -ENOSYS;
}
return 0;
}
static int udpsock_listen(struct socket *s, int backlog) {
return -EINVAL;
}
static int udpsock_recvmsg(struct socket *s, struct msghdr *msg, unsigned int flags) {
struct pbuf *p;
struct udp_hdr *udphdr;
struct ip_hdr *iphdr;
void *buf;
int len;
int rc;
struct sockaddr_in *sin;
struct sockreq req;
if (msg->msg_name && msg->msg_namelen < sizeof(struct sockaddr_in)) return -EFAULT;
if (!s->udp.pcb || s->udp.pcb->local_port == 0) return -EINVAL;
p = s->udp.recvhead;
if (p) {
s->udp.recvhead = pbuf_dechain(p);
if (!s->udp.recvhead) s->udp.recvtail = NULL;
if (!s->udp.recvhead) clear_io_event(&s->iob, IOEVT_READ);
buf = p->payload;
len = p->len;
pbuf_header(p, UDP_HLEN);
udphdr = p->payload;
//FIXME: this does not work if there are options in the ip header
pbuf_header(p, IP_HLEN);
iphdr = p->payload;
rc = write_iovec(msg->msg_iov, msg->msg_iovlen, buf, len);
if (rc < len) rc = -EMSGSIZE;
if (msg->msg_name) {
sin = (struct sockaddr_in *) msg->msg_name;
sin->sin_family = AF_INET;
sin->sin_port = udphdr->src;
sin->sin_addr.s_addr = iphdr->src.addr;
}
msg->msg_namelen = sizeof(struct sockaddr_in);
pbuf_free(p);
} else if (s->flags & SOCK_NBIO) {
rc = -EAGAIN;
} else {
rc = submit_socket_request(s, &req, SOCKREQ_RECV, msg, s->rcvtimeo);
}
return rc;
}
static int udpsock_sendmsg(struct socket *s, struct msghdr *msg, unsigned int flags) {
struct pbuf *p;
int size;
int rc;
if (msg->msg_name && msg->msg_namelen < sizeof(struct sockaddr_in)) return -EFAULT;
if (!s->udp.pcb || s->udp.pcb->local_port == 0) {
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
rc = udpsock_bind(s, (struct sockaddr *) &sin, sizeof(sin));
if (rc < 0) return rc;
}
size = get_iovec_size(msg->msg_iov, msg->msg_iovlen);
p = pbuf_alloc(PBUF_TRANSPORT, size, PBUF_RW);
if (!p) return -ENOMEM;
rc = read_iovec(msg->msg_iov, msg->msg_iovlen, p->payload, size);
if (rc < 0) return rc;
if (msg->msg_name) {
struct sockaddr_in *sin = (struct sockaddr_in *) msg->msg_name;
rc = udp_send(s->udp.pcb, p, (struct ip_addr *) &sin->sin_addr, ntohs(sin->sin_port), NULL);
} else {
rc = udp_send(s->udp.pcb, p, NULL, 0, NULL);
}
if (rc < 0)
{
pbuf_free(p);
return rc;
}
return size;
}
static int udpsock_setsockopt(struct socket *s, int level, int optname, const void *optval, int optlen) {
if (level == SOL_SOCKET) {
switch (optname) {
case SO_BROADCAST:
if (optlen != 4) return -EINVAL;
if (*(int *) optval) {
s->flags |= SOCK_BCAST;
if (s->udp.pcb) s->udp.pcb->flags |= UDP_FLAGS_BROADCAST;
} else {
s->flags &= ~SOCK_BCAST;
if (s->udp.pcb) s->udp.pcb->flags &= ~UDP_FLAGS_BROADCAST;
}
break;
case SO_SNDTIMEO:
if (optlen != 4) return -EINVAL;
s->sndtimeo = *(unsigned int *) optval;
break;
case SO_RCVTIMEO:
if (optlen != 4) return -EINVAL;
s->rcvtimeo = *(unsigned int *) optval;
break;
default:
return -ENOPROTOOPT;
}
} else {
return -ENOPROTOOPT;
}
return 0;
}
static int udpsock_shutdown(struct socket *s, int how) {
return -ENOSYS;
}
static int udpsock_socket(struct socket *s, int domain, int type, int protocol) {
set_io_event(&s->iob, IOEVT_WRITE);
return 0;
}
struct sockops udpops = {
udpsock_accept,
udpsock_bind,
udpsock_close,
udpsock_connect,
udpsock_getpeername,
udpsock_getsockname,
udpsock_getsockopt,
udpsock_ioctl,
udpsock_listen,
udpsock_recvmsg,
udpsock_sendmsg,
udpsock_setsockopt,
udpsock_shutdown,
udpsock_socket,
};