From 1fd04557c706a5d0d35bca6e179edc1af6bbd9bb Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Tue, 13 Oct 2015 19:12:12 -0400 Subject: [PATCH] Refactored connect(), added checks to socket(), updated checklists --- netcon/NetconEthernetTap.cpp | 56 +++++++++++++++++------- netcon/intercept.c | 82 ++++++++++++++++++++++++----------- netcon/libintercept.so.1.0 | Bin 53400 -> 53656 bytes 3 files changed, 97 insertions(+), 41 deletions(-) diff --git a/netcon/NetconEthernetTap.cpp b/netcon/NetconEthernetTap.cpp index 5ef52b72..0921f460 100644 --- a/netcon/NetconEthernetTap.cpp +++ b/netcon/NetconEthernetTap.cpp @@ -503,7 +503,7 @@ int NetconEthernetTap::send_return_value(int fd, int retval, int _errno = 0) [I] EBADF - The descriptor is invalid. [I] ECONNABORTED - A connection has been aborted. [i] 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). + [-] 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). [I] EINVAL - (accept4()) invalid value in flags. [I] EMFILE - The per-process limit of open file descriptors has been reached. @@ -806,6 +806,12 @@ void NetconEthernetTap::handle_retval(PhySocket *sock, void **uptr, unsigned cha * @param structure containing the data and parameters for this client's RPC * + i := should be implemented in intercept lib + I := is implemented in intercept lib + X := is implemented in service + ? := required treatment Unknown + - := Not needed + [ ] EACCES - The address is protected, and the user is not the superuser. [X] EADDRINUSE - The given address is already in use. [I] EBADF - sockfd is not a valid descriptor. @@ -876,10 +882,16 @@ void NetconEthernetTap::handle_bind(PhySocket *sock, void **uptr, struct bind_st * @param structure containing the data and parameters for this client's RPC * - [?] EADDRINUSE - Another socket is already listening on the same port. - [X] EBADF - The argument sockfd is not a valid descriptor. - [i] ENOTSOCK - The argument sockfd is not a socket. - [i] EOPNOTSUPP - The socket is not of a type that supports the listen() operation. + i := should be implemented in intercept lib + I := is implemented in intercept lib + X := is implemented in service + ? := required treatment Unknown + - := Not needed + +[?] EADDRINUSE - Another socket is already listening on the same port. +[I] EBADF - The argument sockfd is not a valid descriptor. +[I] ENOTSOCK - The argument sockfd is not a socket. +[I] EOPNOTSUPP - The socket is not of a type that supports the listen() operation. */ void NetconEthernetTap::handle_listen(PhySocket *sock, void **uptr, struct listen_st *listen_rpc) @@ -890,6 +902,10 @@ void NetconEthernetTap::handle_listen(PhySocket *sock, void **uptr, struct liste fprintf(stderr, "handle_listen(): PCB is already in listening state.\n"); return; } + + // TODO: Implement liste_with_backlog + // FIXME: Correct return values from this method, most is handled in intercept lib + struct tcp_pcb* listening_pcb = lwipstack->tcp_listen(conn->pcb); if(listening_pcb != NULL) { conn->pcb = listening_pcb; @@ -924,13 +940,17 @@ void NetconEthernetTap::handle_listen(PhySocket *sock, void **uptr, struct liste * @param structure containing the data and parameters for this client's RPC * - TODO: set errno appropriately + i := should be implemented in intercept lib + I := is implemented in intercept lib + X := is implemented in service + ? := required treatment Unknown + - := Not needed [-] EACCES - Permission to create a socket of the specified type and/or protocol is denied. - [?] EAFNOSUPPORT - The implementation does not support the specified address family. - [?] EINVAL - Unknown protocol, or protocol family not available. - [?] EINVAL - Invalid flags in type. - [i] EMFILE - Process file table overflow. + [I] EAFNOSUPPORT - The implementation does not support the specified address family. + [I] EINVAL - Unknown protocol, or protocol family not available. + [I] EINVAL - Invalid flags in type. + [I] EMFILE - Process file table overflow. [i] ENFILE - The system limit on the total number of open files has been reached. [X] ENOBUFS or ENOMEM - Insufficient memory is available. The socket cannot be created until sufficient resources are freed. [?] EPROTONOSUPPORT - The protocol type or the specified protocol is not supported within this domain. @@ -974,23 +994,29 @@ void NetconEthernetTap::handle_socket(PhySocket *sock, void **uptr, struct socke * @param PhySocket associated with this RPC connection * @param structure containing the data and parameters for this client's RPC - --- Error handling in this method will only catch problems which are immeidately + --- Error handling in this method will only catch problems which are immedately apprent. Some errors will need to be caught in the nc_connected(0 callback - [i] EACCES - For UNIX domain sockets, which are identified by pathname: Write permission is denied ... + i := should be implemented in intercept lib + I := is implemented in intercept lib + X := is implemented in service + ? := required treatment Unknown + - := Not needed + + [-] EACCES - For UNIX domain sockets, which are identified by pathname: Write permission is denied ... [ ] EACCES, EPERM - The user tried to connect to a broadcast address without having the socket broadcast flag enabled ... [i] EADDRINUSE - Local address is already in use. [?] EAFNOSUPPORT - The passed address didn't have the correct address family in its sa_family field. [ ] EAGAIN - No more free local ports or insufficient entries in the routing cache. [ ] EALREADY - The socket is nonblocking and a previous connection attempt has not yet been completed. - [ ] EBADF - The file descriptor is not a valid index in the descriptor table. + [I] EBADF - The file descriptor is not a valid index in the descriptor table. [ ] ECONNREFUSED - No-one listening on the remote address. [i] EFAULT - The socket structure address is outside the user's address space. [ ] EINPROGRESS - The socket is nonblocking and the connection cannot be completed immediately. - [?] EINTR - The system call was interrupted by a signal that was caught. + [-] EINTR - The system call was interrupted by a signal that was caught. [X] EISCONN - The socket is already connected. [?] ENETUNREACH - Network is unreachable. - [ ] ENOTSOCK - The file descriptor is not associated with a socket. + [I] ENOTSOCK - The file descriptor is not associated with a socket. [X] ETIMEDOUT - Timeout while attempting connection. * diff --git a/netcon/intercept.c b/netcon/intercept.c index 5060aa2c..4762b309 100755 --- a/netcon/intercept.c +++ b/netcon/intercept.c @@ -528,6 +528,16 @@ int socket(SOCKET_SIG) return -EAFNOSUPPORT; if (socket_type < 0 || socket_type >= SOCK_MAX) return -EINVAL; + /* Check that we haven't hit the soft-limit file descriptors allowed */ + /* FIXME: Find number of open fds + struct rlimit rl; + getrlimit(RLIMIT_NOFILE, &rl); + if(sockfd >= rl.rlim_cur){ + errno = EMFILE; + return -1; + } + */ + /* FIXME: detect ENFILE condition */ #endif #ifdef DUMMY @@ -597,8 +607,21 @@ int socket(SOCKET_SIG) connect() intercept function */ int connect(CONNECT_SIG) { - int err; +#ifdef CHECKS + /* Check that this is a valid fd */ + if(fcntl(__fd, F_GETFD) < 0) { + return -1; + errno = EBADF; + } + /* Check that it is a socket */ + int sock_type; + socklen_t sock_type_len = sizeof(sock_type); + if(getsockopt(__fd, SOL_SOCKET, SO_TYPE, (void *) &sock_type, &sock_type_len) < 0) { + errno = ENOTSOCK; + return -1; + } /* FIXME: Check that address is in user space, return EFAULT ? */ +#endif #ifdef DUMMY dwr("connect(%d)\n", __fd); @@ -606,16 +629,17 @@ int connect(CONNECT_SIG) #else /* make sure we don't touch any standard outputs */ - if(__fd == STDIN_FILENO || __fd == STDOUT_FILENO || __fd == STDERR_FILENO) + if(__fd == STDIN_FILENO || __fd == STDOUT_FILENO || __fd == STDERR_FILENO){ + if (realconnect == NULL) { + dwr("Unresolved symbol: connect(). Library is exiting.\n"); + exit(-1); + } return(realconnect(__fd, __addr, __len)); - int sock_type = -1; - socklen_t sock_type_len = sizeof(sock_type); + } + struct sockaddr_in *connaddr; connaddr = (struct sockaddr_in *) __addr; - getsockopt(__fd, SOL_SOCKET, SO_TYPE, - (void *) &sock_type, &sock_type_len); - if(__addr != NULL && (connaddr->sin_family == AF_LOCAL || connaddr->sin_family == PF_NETLINK || connaddr->sin_family == AF_NETLINK @@ -624,13 +648,9 @@ int connect(CONNECT_SIG) return err; } - char cmd[BUF_SZ]; - if (realconnect == NULL) { - dwr("Unresolved symbol: connect()\n"); - return -1; - } - /* assemble and route command */ + int err; + char cmd[BUF_SZ]; memset(cmd, '\0', BUF_SZ); struct connect_st rpc_st; rpc_st.__tid = syscall(SYS_gettid); @@ -778,7 +798,6 @@ int accept(ACCEPT_SIG) #ifdef CHECKS /* Check that this is a valid fd */ if(fcntl(sockfd, F_GETFD) < 0) { - dwr("EBADF\n"); return -1; errno = EBADF; } @@ -787,13 +806,11 @@ int accept(ACCEPT_SIG) socklen_t sock_type_len = sizeof(sock_type); if(getsockopt(sockfd, SOL_SOCKET, SO_TYPE, (void *) &sock_type, &sock_type_len) < 0) { errno = ENOTSOCK; - dwr("ENOTSOCK\n"); return -1; } /* Check that this socket supports accept() */ if(!(sock_type && (SOCK_STREAM | SOCK_SEQPACKET))) { errno = EOPNOTSUPP; - dwr("EOPNOTSUPP\n"); return -1; } /* Check that we haven't hit the soft-limit file descriptors allowed */ @@ -801,7 +818,6 @@ int accept(ACCEPT_SIG) getrlimit(RLIMIT_NOFILE, &rl); if(sockfd >= rl.rlim_cur){ errno = EMFILE; - dwr("EMFILE\n"); return -1; } #endif @@ -822,16 +838,12 @@ int accept(ACCEPT_SIG) return -1; } - char gmybuf[16]; - int new_conn_socket; - - char c[1]; - int n = read(sockfd, c, sizeof(c)); + char gmybuf[16], c[1]; + int new_conn_socket, n = read(sockfd, c, sizeof(c)); if(n > 0) { ssize_t size = sock_fd_read(fdret_sock, gmybuf, sizeof(gmybuf), &new_conn_socket); - if(size > 0) - { + if(size > 0) { /* Send our local-fd number back to service so it can complete its mapping table */ memset(cmd, '\0', BUF_SZ); cmd[0] = RPC_FD_MAP_COMPLETION; @@ -869,9 +881,27 @@ int accept(ACCEPT_SIG) listen() intercept function */ int listen(LISTEN_SIG) { + #ifdef CHECKS + /* Check that this is a valid fd */ + if(fcntl(sockfd, F_GETFD) < 0) { + return -1; + errno = EBADF; + } + /* Check that it is a socket */ + int sock_type; + socklen_t sock_type_len = sizeof(sock_type); + if(getsockopt(sockfd, SOL_SOCKET, SO_TYPE, (void *) &sock_type, &sock_type_len) < 0) { + errno = ENOTSOCK; + return -1; + } + /* Check that this socket supports accept() */ + if(!(sock_type && (SOCK_STREAM | SOCK_SEQPACKET))) { + errno = EOPNOTSUPP; + return -1; + } + #endif + int err; - /* FIXME: Check that this socket supports listen(), return EOPNOTSUPP */ - /* FIXME: Check that the provided fd is a socket, return ENOTSOCK */ #ifdef DUMMY dwr("listen(%d)\n", sockfd); diff --git a/netcon/libintercept.so.1.0 b/netcon/libintercept.so.1.0 index 1dc1f0da0ae853d94bd429d850d81e0f1f459607..106c8c2061593866f0a44017a60d57438eff05b8 100755 GIT binary patch delta 14200 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 delta 13674 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