diff --git a/src/safeposix/syscalls/net_calls.rs b/src/safeposix/syscalls/net_calls.rs index 6a25099db..22ebf9af8 100644 --- a/src/safeposix/syscalls/net_calls.rs +++ b/src/safeposix/syscalls/net_calls.rs @@ -2204,22 +2204,106 @@ impl Cage { return 0; } + /// ### Description + /// + /// `accept_syscall` accepts a connection on a socket + /// + /// ### Arguments + /// + /// it accepts two parameters: + /// * `fd` - the file descriptor that refers to the listening socket + /// * `addr` - the address of the incoming connection's socket + /// + /// ### Returns + /// + /// for a successful call, the return value will be a file descriptor for + /// the accepted socket (a nonnegative integer). On error, a negative + /// error number is returned, with the errorno set to represent the + /// corresponding error + /// + /// ### Errors + /// + /// ** EAGAIN or EWOULDBLOCK - The socket is marked nonblocking and no + /// connections are present to be accepted. + /// POSIX.1-2001 and POSIX.1-2008 + /// allow either error to be returned for this case, and do + /// not require these constants to have the same value, so a + /// portable application should check for both possibilities. + /// + /// ** EBADF - sockfd is not an open file descriptor. + /// + /// * ECONNABORTED - A connection has been aborted. + /// + /// * EFAULT - The addr argument is not in a writable part of the user + /// address space. + /// + /// * EINTR - The system call was interrupted by a signal that was caught + /// before a valid connection arrived; see signal(7). + /// + /// ** EINVAL - Socket is not listening for connections, or addrlen is + /// invalid (e.g., is negative). + /// + /// * EMFILE - The per-process limit on the number of open file descriptors + /// has been reached. + /// + /// * ENFILE - The system-wide limit on the total number of open files has + /// been reached. + /// + /// * ENOMEM - Not enough free memory. This often means that the memory + /// allocation is limited by the socket buffer limits, not by the system + /// memory. + /// + /// ** ENOTSOCK - The file descriptor sockfd does not refer to a socket. + /// + /// ** EOPNOTSUPP - The referenced socket is not of type SOCK_STREAM. + /// + /// * EPERM - Firewall rules forbid connection. + /// + /// * EPROTO - Protocol error. + /// + /// In addition, network errors for the new socket and as defined for + /// the protocol may be returned. Various Linux kernels can return + /// other errors such as ENOSR, ESOCKTNOSUPPORT, EPROTONOSUPPORT, + /// ETIMEDOUT. The value ERESTARTSYS may be seen during a trace. + /// + /// ** Indicates the error may be returned from RustPOSIX + /// + /// ### Panics + /// + /// * invalid or out-of-bounds file descriptor), calling unwrap() on it will + /// cause a panic. + /// * Unknown errno value from fcntl returned, will cause panic. + /// + /// for more detailed description of all the commands and return values, see + /// [accept(2)](https://linux.die.net/man/2/accept) pub fn accept_syscall(&self, fd: i32, addr: &mut interface::GenSockaddr) -> i32 { + //If fd is out of range of [0,MAXFD], process will panic + //Otherwise, we obtain a write gaurd to the Option object let checkedfd = self.get_filedescriptor(fd).unwrap(); let mut unlocked_fd = checkedfd.write(); if let Some(filedesc_enum) = &mut *unlocked_fd { + //Find the next available file descriptor and grab a mutable reference + //to the Option object let (newfd, guardopt) = self.get_next_fd(None); + //In the case that no fd is available from the call to get_next_fd, + //fd is set to -ENFILE = -23 and the error is propagated forward if newfd < 0 { return fd; } let newfdoption: &mut Option = &mut *guardopt.unwrap(); + //Pattern match such that FileDescriptor object must be the Socket variant + //Otherwise, return with an err as the fd refers to something other than a + // socket match filedesc_enum { Socket(ref mut sockfdobj) => { + //Clone the socket handle as it may be in use by other threads and + //obtain a read lock, blocking the calling thread until + //there are no other writers that hold the lock let sock_tmp = sockfdobj.handle.clone(); let mut sockhandle = sock_tmp.read(); - // check if domain socket + //Match the domain of the socket to accept the connection match sockhandle.domain { AF_UNIX => { return self.accept_unix( @@ -2251,16 +2335,28 @@ impl Cage { _ => { return syscall_error( Errno::ENOTSOCK, - "listen", + "accept", "file descriptor refers to something other than a socket", ); } } } else { - return syscall_error(Errno::EBADF, "listen", "invalid file descriptor"); + return syscall_error(Errno::EBADF, "accept", "invalid file descriptor"); } } + //The function accepts a connection over the Unix domain + // + //Args: sockhandle is a mut reference to a read lock on the SocketHandle of the + // listening socket sockfdobj is a mut reference to the Socket + // Description of the listening socket newfd is an available file + // descriptor newfdoption is a mut reference to a + // Option object at the newfd index in the + // file descriptor table addr is the address of the incoming + // connection's socket + // + //upon success return newfd, the new socket file descriptor from the "server + // side" otherwise, return -errno with errno set to the error fn accept_unix( &self, sockhandle: &mut interface::RustLockReadGuard, @@ -2270,6 +2366,8 @@ impl Cage { addr: &mut interface::GenSockaddr, ) -> i32 { match sockhandle.protocol { + //UDP Sockets do not support listening as UDP is a + //connectionless based protocol IPPROTO_UDP => { return syscall_error( Errno::EOPNOTSUPP, @@ -2277,7 +2375,10 @@ impl Cage { "Protocol does not support listening", ); } + //TCP Sockets support listening as TCP is a connection + //based protocol IPPROTO_TCP => { + //Socket must be listening to readily accept a connection if sockhandle.state != ConnState::LISTEN { return syscall_error( Errno::EINVAL, @@ -2285,6 +2386,8 @@ impl Cage { "Socket must be listening before accept is called", ); } + //Initialize a new socket file descriptor and set necessary flags + //based on the listening socket let newsockfd = self._socket_initializer( sockhandle.domain, sockhandle.socktype, @@ -2293,28 +2396,38 @@ impl Cage { sockfdobj.flags & O_CLOEXEC != 0, ConnState::CONNECTED, ); - + //Initialize pipes + //In the Unix domain, lind emulates communcation let remote_addr: interface::GenSockaddr; let sendpipenumber; let receivepipenumber; - + // We loop here to accept the connection. + // If we get a connection object from the accept table, + // we complete the connection and set up the address and pipes. + // If theres no object, we retry, except in the case of + // non-blocking accept where we return EAGAIN loop { + //Normalize the path to the listening socket let localpathbuf = normpath(convpath(sockhandle.localaddr.unwrap().path()), self); + //Note, NET_METADATA.domsock_accept_table stores pending + //connections (from client calling `connect`) + //Retrieve one of the pending connections if it exists let dsconnobj = NET_METADATA.domsock_accept_table.get(&localpathbuf); + //Check if a pending connection exists if let Some(ds) = dsconnobj { - // we loop here to accept the connection - // if we get a connection object from the accept table, we complete the - // connection and set up the address and pipes - // if theres no object, we retry, except in the case of non-blocking accept - // where we return EAGAIN + //Pattern match to retrieve the connvar if let Some(connvar) = ds.get_cond_var() { + //If the incoming connection's socket is not waiting, drop the + //connection loop to the next one if !connvar.broadcast() { drop(ds); continue; } } + //Grab the incoming connection's address, receive pipe, and send pipe, + //and then remove the incoming connection's socket from pending connections let addr = ds.get_sockaddr().clone(); remote_addr = addr.clone(); receivepipenumber = ds.get_receive_pipe().clone(); @@ -2323,8 +2436,9 @@ impl Cage { NET_METADATA.domsock_accept_table.remove(&localpathbuf); break; } else { + //The listening socket is marked nonblocking and no + //connections are present to be accepted if 0 != (sockfdobj.flags & O_NONBLOCK) { - // if non block return EAGAIN return syscall_error( Errno::EAGAIN, "accept", @@ -2334,17 +2448,25 @@ impl Cage { } } + //Gain write access to the socket handle to insert + //info regarding the unix connection let newsock_tmp = newsockfd.handle.clone(); let mut newsockhandle = newsock_tmp.write(); + //Retrieve the inodenum of the incoming connection's socket let pathclone = normpath(convpath(remote_addr.path()), self); if let Some(inodenum) = metawalk(pathclone.as_path()) { + //Insert necessary info about the socket communication newsockhandle.unix_info = Some(UnixSocketInfo { inode: inodenum.clone(), mode: sockhandle.unix_info.as_ref().unwrap().mode, sendpipe: Some(sendpipenumber.clone()), receivepipe: Some(receivepipenumber.clone()), }); + //Grab the incoming connection's socket inode from the inodetable + //and increase the refcount by 1, as the socket is accepting + //a connection. Thus, we do not want the socket to be closed + //before the connection ends. if let Inode::Socket(ref mut sock) = *(FS_METADATA.inodetable.get_mut(&inodenum).unwrap()) { @@ -2352,21 +2474,38 @@ impl Cage { } }; + //Finalize values for the new "server" socket handle that was + //created to connect with the incoming connection's socket newsockhandle.localaddr = Some(sockhandle.localaddr.unwrap().clone()); newsockhandle.remoteaddr = Some(remote_addr.clone()); newsockhandle.state = ConnState::CONNECTED; + //Insert the socket FileDescriptor object into the + //file descriptor table let _insertval = newfdoption.insert(Socket(newsockfd)); *addr = remote_addr; //populate addr with what address it connected to return newfd; } + //Socket Protocol is not UDP nor TCP, therefore unsupported by lind _ => { return syscall_error(Errno::EOPNOTSUPP, "accept", "Unkown protocol in accept"); } } } + //The function accepts a connection over the INET domain + // + //Args: sockhandle is a mut reference to a read lock on the SocketHandle of the + // listening socket sockfdobj is a mut reference to the Socket + // Description of the listening socket newfd is an available file + // descriptor newfdoption is a mut reference to a + // Option object at the newfd index in the + // file descriptor table addr is the address of the incoming + // connection's socket + // + //upon success return newfd, the new socket file descriptor from the "server + // side" otherwise, return -errno with errno set to the error fn accept_inet( &self, sockhandle: &mut interface::RustLockReadGuard, @@ -2376,6 +2515,8 @@ impl Cage { addr: &mut interface::GenSockaddr, ) -> i32 { match sockhandle.protocol { + //UDP sockets do not support listening as UDP is a connectionless + //based protocol IPPROTO_UDP => { return syscall_error( Errno::EOPNOTSUPP, @@ -2383,7 +2524,10 @@ impl Cage { "Protocol does not support listening", ); } + //TCP Sockets support listening as TCP is a connection + //based protocol IPPROTO_TCP => { + //Socket must be listening to readily accept a connection if sockhandle.state != ConnState::LISTEN { return syscall_error( Errno::EINVAL, @@ -2391,6 +2535,8 @@ impl Cage { "Socket must be listening before accept is called", ); } + //Initialize a new socket file descriptor and set necessary flags + //based on the listening socket let mut newsockfd = self._socket_initializer( sockhandle.domain, sockhandle.socktype, @@ -2400,23 +2546,29 @@ impl Cage { ConnState::CONNECTED, ); + //we loop here so we can cancel blocking accept, + //see comments below and in Socket::new in interface/comm.rs loop { - // we loop here so we can cancel blocking accept, see comments below and in - // Socket::new in interface/comm.rs - // if we got a pending connection in select/poll/whatever, return that here // instead - let ladr = sockhandle.localaddr.unwrap().clone(); //must have been populated by implicit bind + + //Socket must have been populated by implicit bind + let ladr = sockhandle.localaddr.unwrap().clone(); + //Obtain a tuple of the address, port, and port type of the listening socket + //Panics if domain is not INET or INET6 let porttuple = mux_port(ladr.addr().clone(), ladr.port(), sockhandle.domain, TCPPORT); + //Check if there are any pending incoming connections to the listening socket + //and grab the incoming connection's socket raw fd and its address let mut pendingvec = NET_METADATA.pending_conn_table.get_mut(&porttuple).unwrap(); let pendingoption = pendingvec.pop(); let (acceptedresult, remote_addr) = match pendingoption { Some(pendingtup) => pendingtup, None => { - //unwrap ok because listening + //If the socket is blocking, call the accept syscall + //from libc if 0 == (sockfdobj.flags & O_NONBLOCK) { match sockhandle.domain { PF_INET => { @@ -2427,6 +2579,10 @@ impl Cage { } _ => panic!("Unknown domain in accepting socket"), } + //otherwise the socket is nonblocking so call the + // nonblocking accept syscall from libc and + // set the the raw sys fd of the listening socket to + // nonblocking } else { match sockhandle.domain { PF_INET => sockhandle @@ -2445,27 +2601,28 @@ impl Cage { } }; + //If the accept libc call returns with an error if let Err(_) = acceptedresult { match Errno::from_discriminant(interface::get_errno()) { Ok(i) => { //We have the socket timeout set to every one second, so //if our blocking socket ever returns EAGAIN, it must be - //the case that this recv timeout was exceeded, and we + //the case that this timeout was exceeded, and we //should thus not treat this as a failure in our emulated //socket; see comment in Socket::new in interface/comm.rs if sockfdobj.flags & O_NONBLOCK == 0 && i == Errno::EAGAIN { + // if the cancel status is set in the cage, we trap around a + // cancel point + // until the individual thread is signaled to kill itself if self .cancelstatus .load(interface::RustAtomicOrdering::Relaxed) { - // if the cancel status is set in the cage, we trap around a - // cancel point - // until the individual thread is signaled to cancel itself loop { interface::cancelpoint(self.cageid); } } - continue; // EAGAIN, try again + continue; } return syscall_error( @@ -2478,9 +2635,10 @@ impl Cage { }; } - // if we get here we have an accepted socket + //If we get here we have an accepted socket let acceptedsock = acceptedresult.unwrap(); - + //Set the address and the port of the new socket + //created to handle the connection to the incoming connection's socket let mut newaddr = sockhandle.localaddr.unwrap().clone(); let newport = match NET_METADATA._reserve_localport( newaddr.addr(), @@ -2502,13 +2660,13 @@ impl Cage { newsockhandle.localaddr = Some(newaddr); newsockhandle.remoteaddr = Some(remote_addr.clone()); - //create socket object for new connected socket + //create Socket object for new connected socket newsockhandle.innersocket = Some(acceptedsock); - // set lock-free rawfd for select + //set lock-free rawfd for select newsockfd.rawfd = newsockhandle.innersocket.as_ref().unwrap().raw_sys_fd; let _insertval = newfdoption.insert(Socket(newsockfd)); - *addr = remote_addr; //populate addr with what address it connected to + *addr = remote_addr; //populate addr with the address of the incoming connection's socket return newfd; } diff --git a/src/tests/networking_tests.rs b/src/tests/networking_tests.rs index 82fecb1fe..da94213b3 100644 --- a/src/tests/networking_tests.rs +++ b/src/tests/networking_tests.rs @@ -710,10 +710,81 @@ pub mod net_tests { lindrustfinalize(); } + #[test] + //Attempt to call accept on a socket that is not listening for a connection + //Attempt to call accept on a socket that is closed + //Both of these should return their respective errors, being EINVAL and EBADF + pub fn ut_lind_net_accept_errs() { + //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, + // and also performs clean env setup + let _thelock = setup::lock_and_init(); + + let cage = interface::cagetable_getref(1); + + let serversockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + let clientsockfd = cage.socket_syscall(AF_INET, SOCK_STREAM, 0); + + assert!(serversockfd > 0); + assert!(clientsockfd > 0); + let port: u16 = generate_random_port(); + //binding to a socket + let sockaddr = interface::SockaddrV4 { + sin_family: AF_INET as u16, + sin_port: port.to_be(), + sin_addr: interface::V4Addr { + s_addr: u32::from_ne_bytes([127, 0, 0, 1]), + }, + padding: 0, + }; + let socket = interface::GenSockaddr::V4(sockaddr); //127.0.0.1 + assert_eq!(cage.bind_syscall(serversockfd, &socket), 0); + + //forking the cage to get another cage with the same information + assert_eq!(cage.fork_syscall(2), 0); + + let thread = interface::helper_thread(move || { + let cage2 = interface::cagetable_getref(2); + let mut socket2 = interface::GenSockaddr::V4(interface::SockaddrV4::default()); + + //Accept before listening + assert_eq!( + cage2.accept_syscall(serversockfd, &mut socket2), + -(Errno::EINVAL as i32) + ); + //Now listen + assert_eq!(cage2.listen_syscall(serversockfd, 10), 0); + + //Check that fd > 0, indicating a valid connection + assert!(cage2.accept_syscall(serversockfd, &mut socket2) > 0); + //Close serverfd and check that it doesn't accept + assert_eq!(cage2.close_syscall(serversockfd), 0); + assert_eq!( + cage2.accept_syscall(serversockfd, &mut socket2), + -(Errno::EBADF as i32) + ); + + assert_eq!(cage2.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + }); + + interface::sleep(interface::RustDuration::from_millis(1000)); + assert_eq!(cage.connect_syscall(clientsockfd, &socket), 0); + + let mut retsocket = interface::GenSockaddr::V4(interface::SockaddrV4::default()); + assert_eq!(cage.getsockname_syscall(clientsockfd, &mut retsocket), 0); + assert_ne!(retsocket, socket); + + assert_eq!(cage.close_syscall(serversockfd), 0); + + thread.join().unwrap(); + + assert_eq!(cage.exit_syscall(EXIT_SUCCESS), EXIT_SUCCESS); + lindrustfinalize(); + } + + #[test] //We try to queue more than 1 connection with the //backlog is set to 1. We expect both connections to return with //errno set to EINPROGRESS as the sockets are non-blocking - #[test] pub fn ut_lind_net_listen_more_than_backlog() { //acquiring a lock on TESTMUTEX prevents other tests from running concurrently, // and also performs clean env setup