| /* |
| * Copyright (c) 2011 The Regents of the University of California |
| * David Zhu <yuzhu@cs.berkeley.edu> |
| * See LICENSE for details. |
| * |
| * Socket layer on top of TCP abstraction. Similar to the BSD implementation. |
| * |
| */ |
| #include <ros/common.h> |
| #include <socket.h> |
| #include <vfs.h> |
| #include <time.h> |
| #include <kref.h> |
| #include <syscall.h> |
| #include <sys/uio.h> |
| #include <ros/errno.h> |
| #include <net.h> |
| #include <net/udp.h> |
| #include <net/tcp.h> |
| #include <net/pbuf.h> |
| #include <net/tcp_impl.h> |
| #include <umem.h> |
| #include <kthread.h> |
| #include <bitmask.h> |
| #include <debug.h> |
| /* |
| *TODO: Figure out which socket.h is used where |
| *There are several socket.h in kern, and a couple more in glibc. Perhaps the glibc ones |
| *should grab from here.. |
| */ |
| |
| struct kmem_cache *sock_kcache; |
| struct kmem_cache *mbuf_kcache; |
| struct kmem_cache *udp_pcb_kcache; |
| struct kmem_cache *tcp_pcb_kcache; |
| struct kmem_cache *tcp_pcb_listen_kcache; |
| struct kmem_cache *tcp_segment_kcache; |
| |
| // file ops needed to support read/write on socket fd |
| static struct file_operations socket_op = { |
| 0, |
| 0,//soo_read, |
| 0,//soo_write, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| 0,//soo_poll, |
| 0, |
| 0, |
| 0, // sendpage might apply here |
| 0, |
| }; |
| static struct socket* getsocket(struct proc *p, int fd){ |
| /* look up fd -> file */ |
| struct file *so_file = get_file_from_fd(&(p->open_files), fd); |
| |
| /* get socket and verify its type */ |
| if (so_file == NULL){ |
| printd("getsocket() fd -> null file: fd %d\n", fd); |
| return NULL; |
| } |
| if (so_file->f_op != &socket_op) { |
| set_errno(ENOTSOCK); |
| printd("fd %d maps to non-socket file\n"); |
| return NULL; |
| } else |
| return (struct socket*) so_file->f_privdata; |
| } |
| |
| struct socket* alloc_sock(int socket_family, int socket_type, int protocol){ |
| struct socket *newsock = kmem_cache_alloc(sock_kcache, 0); |
| assert(newsock); |
| |
| newsock->so_family = socket_family; |
| newsock->so_type = socket_type; |
| newsock->so_protocol = protocol; |
| newsock->so_state = SS_ISDISCONNECTED; |
| STAILQ_INIT(&(newsock->acceptq)); |
| pbuf_head_init(&newsock->recv_buff); |
| pbuf_head_init(&newsock->send_buff); |
| sem_init_irqsave(&newsock->sem, 0); |
| sem_init_irqsave(&newsock->accept_sem, 0); |
| spinlock_init(&newsock->waiter_lock); |
| LIST_INIT(&newsock->waiters); |
| return newsock; |
| |
| } |
| // TODO: refactor vfs so we can allocate fd and do the basic initialization |
| struct file *alloc_socket_file(struct socket* sock) { |
| struct file *file = alloc_file(); |
| if (file == NULL) return 0; |
| |
| // Linux fakes a dentry and an inode for socks, see socket.c : sock_alloc_file |
| file->f_dentry = NULL; // This might break things? |
| file->f_vfsmnt = 0; |
| file->f_flags = 0; |
| |
| file->f_mode = S_IRUSR | S_IWUSR; // both read and write for socket files |
| |
| file->f_pos = 0; |
| file->f_uid = 0; |
| file->f_gid = 0; |
| file->f_error = 0; |
| |
| file->f_op = &socket_op; |
| file->f_privdata = sock; |
| file->f_mapping = 0; |
| return file; |
| } |
| |
| void socket_init(){ |
| |
| /* allocate buf for socket */ |
| sock_kcache = kmem_cache_create("socket", sizeof(struct socket), |
| __alignof__(struct socket), 0, 0, 0); |
| udp_pcb_kcache = kmem_cache_create("udppcb", sizeof(struct udp_pcb), |
| __alignof__(struct udp_pcb), 0, 0, 0); |
| tcp_pcb_kcache = kmem_cache_create("tcppcb", sizeof(struct tcp_pcb), |
| __alignof__(struct tcp_pcb), 0, 0, 0); |
| tcp_pcb_listen_kcache = kmem_cache_create("tcppcblisten", sizeof(struct tcp_pcb_listen), |
| __alignof__(struct tcp_pcb_listen), 0, 0, 0); |
| tcp_segment_kcache = kmem_cache_create("tcpsegment", sizeof(struct tcp_seg), |
| __alignof__(struct tcp_seg), 0, 0, 0); |
| pbuf_init(); |
| |
| } |
| intreg_t sys_accept(struct proc *p, int sockfd, struct sockaddr *addr, socklen_t *addrlen) { |
| printk ("sysaccept called\n"); |
| struct socket* sock = getsocket(p, sockfd); |
| struct sockaddr_in *in_addr = (struct sockaddr_in *)addr; |
| uint16_t r_port; |
| struct socket *accepted = NULL; |
| int8_t irq_state = 0; |
| if (sock == NULL) { |
| set_errno(EBADF); |
| return -1; |
| } |
| if (sock->so_type == SOCK_DGRAM){ |
| return -1; // indicates false for connect |
| } else if (sock->so_type == SOCK_STREAM) { |
| /* XXX these do the same thing, what is it you actually wanted to do? |
| * (Originally the first was sleep_on, and the second __down_sem */ |
| if (STAILQ_EMPTY(&(sock->acceptq))) { |
| // block on the acceptq |
| sem_down_irqsave(&sock->accept_sem, &irq_state); |
| } else { |
| sem_down_irqsave(&sock->accept_sem, &irq_state); |
| } |
| spin_lock_irqsave(&sock->waiter_lock); |
| accepted = STAILQ_FIRST(&(sock->acceptq)); |
| STAILQ_REMOVE_HEAD((&(sock->acceptq)), next); |
| spin_unlock_irqsave(&sock->waiter_lock); |
| if (accepted == NULL) return -1; |
| struct file *file = alloc_socket_file(accepted); |
| if (file == NULL) return -1; |
| int fd = insert_file(&p->open_files, file, 0); |
| if (fd < 0) { |
| warn("File insertion for socket open failed"); |
| return -1; |
| } |
| kref_put(&file->f_kref); |
| } |
| return -1; |
| } |
| |
| static error_t accept_callback(void *arg, struct tcp_pcb *newpcb, error_t err) { |
| struct socket *sockold = (struct socket *) arg; |
| struct socket *sock = alloc_sock(sockold->so_family, sockold->so_type, sockold->so_protocol); |
| int8_t irq_state = 0; |
| |
| sock->so_pcb = newpcb; |
| newpcb->pcbsock = sock; |
| spin_lock_irqsave(&sockold->waiter_lock); |
| STAILQ_INSERT_TAIL(&sockold->acceptq, sock, next); |
| // wake up any kthread who is potentially waiting |
| spin_unlock_irqsave(&sockold->waiter_lock); |
| sem_up_irqsave(&sock->accept_sem, &irq_state); |
| return 0; |
| } |
| intreg_t sys_listen(struct proc *p, int sockfd, int backlog) { |
| struct socket* sock = getsocket(p, sockfd); |
| if (sock == NULL) { |
| set_errno(EBADF); |
| return -1; |
| } |
| if (sock->so_type == SOCK_DGRAM){ |
| return -1; // indicates false for connect |
| } else if (sock->so_type == SOCK_STREAM) { |
| // check if the socket is in WAIT state |
| struct tcp_pcb *tpcb = (struct tcp_pcb*)sock->so_pcb; |
| struct tcp_pcb* lpcb = tcp_listen_with_backlog(tpcb, backlog); |
| if (lpcb == NULL) { |
| return -1; |
| } |
| sock->so_pcb = lpcb; |
| |
| // register callback for new connection |
| tcp_arg(lpcb, sock); |
| tcp_accept(lpcb, accept_callback); |
| |
| return 0; |
| |
| |
| // XXX: add backlog later |
| } |
| return -1; |
| } |
| intreg_t sys_connect(struct proc *p, int sock_fd, const struct sockaddr* addr, int addrlen) { |
| printk("sys_connect called \n"); |
| struct socket* sock = getsocket(p, sock_fd); |
| struct sockaddr_in *in_addr = (struct sockaddr_in *)addr; |
| uint16_t r_port; |
| if (sock == NULL) { |
| set_errno(EBADF); |
| return -1; |
| } |
| if (sock->so_type == SOCK_DGRAM){ |
| return -1; // indicates false for connect |
| } else if (sock->so_type == SOCK_STREAM) { |
| error_t err = tcp_connect((struct tcp_pcb*)sock->so_pcb, & (in_addr->sin_addr), in_addr->sin_port, NULL); |
| return err; |
| } |
| |
| return -1; |
| } |
| |
| intreg_t sys_send(struct proc *p, int sockfd, const void *buf, size_t len, |
| int flags) { |
| printk("sys_send called \n"); |
| struct socket* sock = getsocket(p, sockfd); |
| const struct sockaddr_in *in_addr = (const struct sockaddr_in *)buf; |
| uint16_t r_port; |
| if (sock == NULL) { |
| set_errno(EBADF); |
| return -1; |
| } |
| return len; |
| |
| } |
| intreg_t sys_recv(struct proc *p, int sockfd, void *buf, size_t len, int flags) { |
| printk("sys_recv called \n"); |
| // return actual length filled |
| return len; |
| } |
| |
| intreg_t sys_bind(struct proc* p_proc, int fd, const struct sockaddr *addr, socklen_t addrlen) { |
| struct socket* sock = getsocket(p_proc, fd); |
| const struct sockaddr_in *in_addr = (const struct sockaddr_in *)addr; |
| uint16_t r_port; |
| if (sock == NULL) { |
| set_errno(EBADF); |
| return -1; |
| } |
| if (sock->so_type == SOCK_DGRAM){ |
| return udp_bind((struct udp_pcb*)sock->so_pcb, & (in_addr->sin_addr), in_addr->sin_port); |
| } else if (sock->so_type == SOCK_STREAM) { |
| return tcp_bind((struct tcp_pcb*)sock->so_pcb, & (in_addr->sin_addr), in_addr->sin_port); |
| } else { |
| printk("SOCK type not supported in bind operation \n"); |
| return -1; |
| } |
| return 0; |
| } |
| |
| intreg_t sys_socket(struct proc *p, int socket_family, int socket_type, int protocol){ |
| //check validity of params |
| if (socket_family != AF_INET && socket_type != SOCK_DGRAM) |
| return 0; |
| struct socket *sock = alloc_sock(socket_family, socket_type, protocol); |
| if (socket_type == SOCK_DGRAM){ |
| /* udp socket */ |
| sock->so_pcb = udp_new(); |
| /* back link */ |
| ((struct udp_pcb*) (sock->so_pcb))->pcbsock = sock; |
| } else if (socket_type == SOCK_STREAM) { |
| /* tcp socket */ |
| sock->so_pcb = tcp_new(); |
| ((struct tcp_pcb*) (sock->so_pcb))->pcbsock = sock; |
| } |
| struct file *file = alloc_socket_file(sock); |
| |
| if (file == NULL) return -1; |
| int fd = insert_file(&p->open_files, file, 0); |
| if (fd < 0) { |
| warn("File insertion for socket open failed"); |
| return -1; |
| } |
| kref_put(&file->f_kref); |
| printk("Socket open, res = %d\n", fd); |
| return fd; |
| } |
| |
| intreg_t send_iov(struct socket* sock, struct iovec* iov, int flags){ |
| // COPY_COUNT: for each iov, copy into mbuf, and send |
| // should not copy here, copy in the protocol.. |
| // should be esomething like this sock->so_proto->pr_send(sock, iov, flags); |
| // make it datagram specific for now... |
| send_datagram(sock, iov, flags); |
| // finally time to check for validity of UA, in the protocol send |
| return 0; |
| } |
| |
| /*TODO: iov support currently broken */ |
| int send_datagram(struct socket* sock, struct iovec* iov, int flags){ |
| // is this a connection oriented protocol? |
| struct pbuf *prev = NULL; |
| struct pbuf *curr = NULL; |
| if (sock->so_type == SOCK_STREAM){ |
| set_errno(ENOTCONN); |
| return -1; |
| } |
| |
| // possible sock locks needed |
| if ((sock->so_state & SS_ISCONNECTED) == 0){ |
| set_errno(EINVAL); |
| return -1; |
| } |
| // pbuf_ref needs to map in the user ref |
| for (int i = 0; i< sizeof(iov) / sizeof (struct iovec); i++){ |
| prev = curr; |
| curr = pbuf_alloc(PBUF_TRANSPORT, iov[i].iov_len, PBUF_REF); |
| if (prev!=NULL) pbuf_chain(prev, curr); |
| } |
| // struct pbuf* pb = pbuf_alloc(PBUF_TRANSPORT, PBUF_REF); |
| udp_send(sock->so_pcb, prev); |
| return 0; |
| |
| } |
| |
| /* sys_sendto can send SOCK_DGRAM and eventually SOCK_STREAM |
| * SOCK_DGRAM uses PBUF_REF since UDP does not need to wait for ack |
| * SOCK_STREAM uses PBUF_ |
| * |
| */ |
| intreg_t sys_sendto(struct proc *p_proc, int fd, const void *buffer, size_t length, |
| int flags, const struct sockaddr *dest_addr, socklen_t dest_len){ |
| // look up the socket |
| struct socket* sock = getsocket(p_proc, fd); |
| int error; |
| struct sockaddr_in *in_addr; |
| uint16_t r_port; |
| if (sock == NULL) { |
| set_errno(EBADF); |
| return -1; |
| } |
| if (sock->so_type == SOCK_DGRAM){ |
| in_addr = (struct sockaddr_in *)dest_addr; |
| struct pbuf* buf = pbuf_alloc(PBUF_TRANSPORT, length, PBUF_REF); |
| if (buf != NULL) |
| buf->payload = (void*)buffer; |
| else |
| warn("pbuf alloc failed \n"); |
| // potentially unsafe cast to udp_pcb |
| return udp_sendto((struct udp_pcb*) sock->so_pcb, buf, &in_addr->sin_addr, in_addr->sin_port); |
| } |
| |
| return -1; |
| //TODO: support for sendmsg and iovectors? Let's get the basics working first! |
| #if 0 |
| // use iovector to handle sendmsg calls too, and potentially scatter-gather |
| struct msghdr msg; |
| struct iovec iov; |
| struct uio auio; |
| |
| // checking for permission only when you are sending it |
| // potential bug TOCTOU, especially with async calls |
| |
| msg.msg_name = dest_addr; |
| msg.msg_namelen = dest_len; |
| msg.msg_iov = &iov; |
| msg.msg_iovlen = 1; |
| msg.msg_control = 0; |
| |
| iov.iov_base = buffer; |
| iov.iov_len = length; |
| |
| |
| // this is why we need another function to populate auio |
| |
| auio.uio_iov = iov; |
| auio.uio_iovcnt = 1; |
| auio.uio_offset = 0; |
| auio.uio_resid = 0; |
| auio.uio_rw = UIO_WRITE; |
| auio.uio_proc = p; |
| |
| // consider changing to send_uaio, since we care about progress. |
| error = send_iov(soc, iov, flags); |
| #endif |
| } |
| |
| /* UDP and TCP has different waiting semantics |
| * UDP requires any packet to be available. |
| * TCP requires accumulation of certain size? |
| */ |
| intreg_t sys_recvfrom(struct proc *p, int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len){ |
| struct socket* sock = getsocket(p, socket); |
| int copied = 0; |
| int returnval = 0; |
| int8_t irq_state = 0; |
| if (sock == NULL) { |
| set_errno(EBADF); |
| return -1; |
| } |
| if (sock->so_type == SOCK_DGRAM){ |
| struct pbuf_head *ph = &(sock->recv_buff); |
| struct pbuf* buf = NULL; |
| buf = detach_pbuf(ph); |
| if (!buf){ |
| // about to sleep |
| sem_down_irqsave(&sock->sem, &irq_state); |
| buf = detach_pbuf(ph); |
| // Someone woke me up, there should be data.. |
| assert(buf); |
| } else { |
| sem_down_irqsave(&sock->sem, &irq_state); |
| } |
| copied = buf->len - sizeof(struct udp_hdr); |
| if (copied > length) |
| copied = length; |
| pbuf_header(buf, -UDP_HDR_SZ); |
| // copy it to user space |
| returnval = memcpy_to_user_errno(p, buffer, buf->payload, copied); |
| } |
| if (returnval < 0) |
| return -1; |
| else |
| return copied; |
| } |
| |
| static int selscan(int maxfdp1, fd_set *readset_in, fd_set *writeset_in, fd_set *exceptset_in, |
| fd_set *readset_out, fd_set *writeset_out, fd_set *exceptset_out){ |
| return 0; |
| } |
| |
| /* TODO: Start respecting the time out value */ |
| /* TODO: start respecting writefds and exceptfds */ |
| intreg_t sys_select(struct proc *p, int nfds, fd_set *readfds, fd_set *writefds, |
| fd_set *exceptfds, struct timeval *timeout){ |
| /* Create a semaphore */ |
| struct semaphore_entry read_sem; |
| int8_t irq_state = 0; |
| |
| sem_init_irqsave(&(read_sem.sem), 0); |
| |
| /* insert into the sem list of a fd / socket */ |
| int low_fd = 0; |
| for (int i = low_fd; i< nfds; i++) { |
| if(FD_ISSET(i, readfds)){ |
| struct socket* sock = getsocket(p, i); |
| /* if the fd is not open or if the file descriptor is not a socket |
| * go to the next in the fd set |
| */ |
| if (sock == NULL) continue; |
| /* for each file that is open, insert this semaphore to be woken up when there |
| * is data available to be read |
| */ |
| spin_lock(&sock->waiter_lock); |
| LIST_INSERT_HEAD(&sock->waiters, &read_sem, link); |
| spin_unlock(&sock->waiter_lock); |
| } |
| } |
| /* At this point wait on the semaphore */ |
| sem_down_irqsave(&read_sem.sem, &irq_state); |
| /* someone woke me up, so walk through the list of descriptors and find one that is ready */ |
| /* remove itself from all the lists that it is waiting on */ |
| for (int i = low_fd; i<nfds; i++) { |
| if (FD_ISSET(i, readfds)){ |
| struct socket* sock = getsocket(p,i); |
| if (sock == NULL) continue; |
| spin_lock(&sock->waiter_lock); |
| LIST_REMOVE(&read_sem, link); |
| spin_unlock(&sock->waiter_lock); |
| } |
| } |
| fd_set readout, writeout, exceptout; |
| FD_ZERO(&readout); |
| FD_ZERO(&writeout); |
| FD_ZERO(&exceptout); |
| for (int i = low_fd; i< nfds; i ++){ |
| if (readfds && FD_ISSET(i, readfds)){ |
| struct socket* sock = getsocket(p, i); |
| if ((sock->recv_buff).qlen > 0){ |
| FD_SET(i, &readout); |
| } |
| /* if the socket is ready, then we can return it */ |
| } |
| } |
| if (readfds) |
| memcpy(readfds, &readout, sizeof(*readfds)); |
| if (writefds) |
| memcpy(writefds, &writeout, sizeof(*writefds)); |
| if (exceptfds) |
| memcpy(readfds, &readout, sizeof(*readfds)); |
| |
| /* Sleep on that semaphore */ |
| /* Somehow get these file descriptors to wake me up when there is new data */ |
| return 0; |
| } |