socket fs: support non-blocking 'connect()'

Fixes #3113
This commit is contained in:
Christian Prochaska 2019-01-11 14:56:07 +01:00 committed by Norman Feske
parent 12c10dbcd1
commit e8ea28d897
3 changed files with 210 additions and 34 deletions

View File

@ -635,7 +635,24 @@ class Vfs::Lxip_connect_file : public Vfs::Lxip_file
** File interface **
********************/
bool poll(bool, Vfs::Vfs_handle::Context *) { return true; }
bool poll(bool trigger_io_response, Vfs::Vfs_handle::Context *context)
{
/*
* The connect file is considered readable when the socket is
* writeable (connected or error).
*/
using namespace Linux;
file f;
f.f_flags = 0;
if (_sock.ops->poll(&f, &_sock, nullptr) & (POLLOUT_SET)) {
if (trigger_io_response)
_parent.trigger_io_response(context);
return true;
}
return false;
}
Lxip::ssize_t write(Lxip_vfs_file_handle &handle,
char const *src, Genode::size_t len,
@ -657,12 +674,12 @@ class Vfs::Lxip_connect_file : public Vfs::Lxip_file
addr->sin_addr.s_addr = get_addr(handle.content_buffer);
addr->sin_family = AF_INET;
_write_err = _sock.ops->connect(&_sock, (sockaddr *)addr, sizeof(addr_storage), 0);
_write_err = _sock.ops->connect(&_sock, (sockaddr *)addr, sizeof(addr_storage), O_NONBLOCK);
switch (_write_err) {
case Lxip::Io_result::LINUX_EINPROGRESS:
_connecting = true;
return -1;
return len;
case Lxip::Io_result::LINUX_EALREADY:
return -1;
@ -679,6 +696,7 @@ class Vfs::Lxip_connect_file : public Vfs::Lxip_file
default:
if (_write_err != 0) return -1;
_is_connected = true;
break;
}
@ -691,6 +709,29 @@ class Vfs::Lxip_connect_file : public Vfs::Lxip_file
return len;
}
Lxip::ssize_t read(Lxip_vfs_file_handle &handle,
char *dst, Genode::size_t len,
file_size /* ignored */) override
{
int so_error = 0;
int opt_len = sizeof(so_error);
int res = sock_getsockopt(&_sock, SOL_SOCKET, SO_ERROR, (char*)&so_error, &opt_len);
if (res != 0) {
Genode::error("Vfs::Lxip_connect_file::read(): getsockopt() failed");
return -1;
}
switch (so_error) {
case 0:
return Genode::snprintf(dst, len, "connected");
case Linux::ECONNREFUSED:
return Genode::snprintf(dst, len, "connection refused");
default:
return Genode::snprintf(dst, len, "unknown error");
}
}
};

View File

@ -114,6 +114,8 @@ struct Socket_fs::Context : Libc::Plugin_context
enum Proto { TCP, UDP };
enum State { UNCONNECTED, ACCEPT_ONLY, CONNECTING, CONNECTED, CONNECT_ABORTED };
struct Inaccessible { }; /* exception */
Absolute_path const path {
@ -141,7 +143,7 @@ struct Socket_fs::Context : Libc::Plugin_context
Proto const _proto;
bool _accept_only = false;
State _state { UNCONNECTED };
template <typename FUNC>
void _fd_apply(FUNC const &fn)
@ -196,7 +198,7 @@ struct Socket_fs::Context : Libc::Plugin_context
}
int data_fd() { return _fd_for_type(Fd::DATA, O_RDWR); }
int connect_fd() { return _fd_for_type(Fd::CONNECT, O_WRONLY); }
int connect_fd() { return _fd_for_type(Fd::CONNECT, O_RDWR); }
int bind_fd() { return _fd_for_type(Fd::BIND, O_WRONLY); }
int listen_fd() { return _fd_for_type(Fd::LISTEN, O_WRONLY); }
int accept_fd() { return _fd_for_type(Fd::ACCEPT, O_RDONLY); }
@ -204,16 +206,57 @@ struct Socket_fs::Context : Libc::Plugin_context
int remote_fd() { return _fd_for_type(Fd::REMOTE, O_RDWR); }
/* request the appropriate fd to ensure the file is open */
bool data_read_ready() { data_fd(); return _fd_read_ready(Fd::DATA); }
bool accept_read_ready() { accept_fd(); return _fd_read_ready(Fd::ACCEPT); }
bool local_read_ready() { local_fd(); return _fd_read_ready(Fd::LOCAL); }
bool remote_read_ready() { remote_fd(); return _fd_read_ready(Fd::REMOTE); }
bool connect_read_ready() { connect_fd(); return _fd_read_ready(Fd::CONNECT); }
bool data_read_ready() { data_fd(); return _fd_read_ready(Fd::DATA); }
bool accept_read_ready() { accept_fd(); return _fd_read_ready(Fd::ACCEPT); }
bool local_read_ready() { local_fd(); return _fd_read_ready(Fd::LOCAL); }
bool remote_read_ready() { remote_fd(); return _fd_read_ready(Fd::REMOTE); }
void accept_only() { _accept_only = true; }
void state(State state) { _state = state; }
State state() const { return _state; }
bool read_ready()
{
return _accept_only ? accept_read_ready() : data_read_ready();
return (_state == ACCEPT_ONLY) ? accept_read_ready() : data_read_ready();
}
bool write_ready()
{
if (_state == CONNECTING)
return connect_read_ready();
/* XXX ask if "data" is writeable */
return true;
}
/*
* Read the connect status from the connect file and return 0 if connected
* or -1 with errno set to the error code.
*/
int read_connect_status()
{
char connect_status[32] = { 0 };
ssize_t connect_status_len;
connect_status_len = read(connect_fd(), connect_status,
sizeof(connect_status));
if (connect_status_len <= 0) {
Genode::error("socket_fs: reading from the connect file failed");
return -1;
}
if (strcmp(connect_status, "connected") == 0)
return 0;
if (strcmp(connect_status, "connection refused") == 0)
return Errno(ECONNREFUSED);
if (strcmp(connect_status, "not connected") == 0)
return Errno(ENOTCONN);
Genode::error("socket_fs: unhandled connection state");
return Errno(ECONNREFUSED);
}
};
@ -566,23 +609,81 @@ extern "C" int socket_fs_connect(int libc_fd, sockaddr const *addr, socklen_t ad
return Errno(EAFNOSUPPORT);
}
/* TODO EISCONN */
/* TODO ECONNREFUSED */
/* TODO maybe EALREADY, EINPROGRESS, ETIMEDOUT */
switch (context->state()) {
case Context::UNCONNECTED:
{
Sockaddr_string addr_string;
try {
addr_string = Sockaddr_string(host_string(*(sockaddr_in const *)addr),
port_string(*(sockaddr_in const *)addr));
}
catch (Address_conversion_failed) { return Errno(EINVAL); }
Sockaddr_string addr_string;
try {
addr_string = Sockaddr_string(host_string(*(sockaddr_in const *)addr),
port_string(*(sockaddr_in const *)addr));
context->state(Context::CONNECTING);
int const len = strlen(addr_string.base());
int const n = write(context->connect_fd(), addr_string.base(), len);
if (n != len) return Errno(ECONNREFUSED);
if (context->fd_flags() & O_NONBLOCK)
return Errno(EINPROGRESS);
/* block until socket is ready for writing */
fd_set writefds;
FD_ZERO(&writefds);
FD_SET(libc_fd, &writefds);
enum { CONNECT_TIMEOUT_S = 10 };
struct timeval timeout {CONNECT_TIMEOUT_S, 0};
int res = select(libc_fd + 1, NULL, &writefds, NULL, &timeout);
if (res < 0) {
/* errno has been set by select() */
return res;
}
if (res == 0) {
context->state(Context::CONNECT_ABORTED);
return Errno(ETIMEDOUT);
}
int connect_status = context->read_connect_status();
if (connect_status == 0)
context->state(Context::CONNECTED);
else
context->state(Context::CONNECT_ABORTED);
/* errno has been set by context->read_connect_status() */
return connect_status;
}
break;
case Context::ACCEPT_ONLY:
return Errno(EINVAL);
case Context::CONNECTING:
{
if (!context->connect_read_ready())
return Errno(EALREADY);
int connect_status = context->read_connect_status();
if (connect_status == 0)
context->state(Context::CONNECTED);
else
context->state(Context::CONNECT_ABORTED);
/* errno was set by context->read_connect_status() */
return connect_status;
}
case Context::CONNECTED:
return Errno(EISCONN);
case Context::CONNECT_ABORTED:
return Errno(ECONNABORTED);
}
catch (Address_conversion_failed) { return Errno(EINVAL); }
int const len = strlen(addr_string.base());
int const n = write(context->connect_fd(), addr_string.base(), len);
if (n != len) return Errno(ECONNREFUSED);
/* sync to block for write completion */
return fsync(context->connect_fd());
return Errno(ECONNREFUSED);
}
@ -599,7 +700,7 @@ extern "C" int socket_fs_listen(int libc_fd, int backlog)
int const n = write(context->listen_fd(), buf, len);
if (n != len) return Errno(EOPNOTSUPP);
context->accept_only();
context->state(Context::ACCEPT_ONLY);
return 0;
}
@ -732,6 +833,21 @@ extern "C" int socket_fs_getsockopt(int libc_fd, int level, int optname,
*(int *)optval = 1;
return 0;
case SO_ERROR:
if (context->state() == Context::CONNECTING) {
int connect_status = context->read_connect_status();
if (connect_status == 0) {
*(int*)optval = 0;
context->state(Context::CONNECTED);
} else {
*(int*)optval = errno;
context->state(Context::CONNECT_ABORTED);
}
return 0;
}
/* not yet implemented - but return true */
*(int *)optval = 0;
return 0;
@ -948,10 +1064,14 @@ int Socket_fs::Plugin::select(int nfds,
}
if (FD_ISSET(fd, &in_writefds)) {
if (true /* XXX ask if "data" is writeable */) {
FD_SET(fd, writefds);
++nready;
}
try {
Socket_fs::Context *context = dynamic_cast<Socket_fs::Context *>(fdo->context);
if (context->write_ready()) {
FD_SET(fd, writefds);
++nready;
}
} catch (Socket_fs::Context::Inaccessible) { }
}
/* XXX exceptfds not supported */

View File

@ -827,9 +827,11 @@ class Lwip::Udp_socket_dir final :
case Lwip_file_handle::CONNECT: {
/* check if the PCB was connected */
if (ip_addr_isany(&_pcb->remote_ip))
return Read_result::READ_OK;
/* otherwise fallthru to REMOTE*/
if (!ip_addr_isany(&_pcb->remote_ip))
out_count = Genode::snprintf(dst, count, "connected");
else
out_count = Genode::snprintf(dst, count, "not connected");
return Read_result::READ_OK;
}
case Lwip_file_handle::REMOTE: {
@ -1162,7 +1164,11 @@ class Lwip::Tcp_socket_dir final :
break;
case Lwip_file_handle::CONNECT:
return !ip_addr_isany(&_pcb->remote_ip);
/*
* The connect file is considered readable when the socket is
* writeable (connected or error).
*/
return ((state == READY) || (state == CLOSED));
case Lwip_file_handle::LOCATION:
case Lwip_file_handle::LOCAL:
@ -1303,6 +1309,15 @@ class Lwip::Tcp_socket_dir final :
break;
case Lwip_file_handle::CONNECT:
switch (state) {
case READY:
out_count = Genode::snprintf(dst, count, "connected");
break;
default:
out_count = Genode::snprintf(dst, count, "connection refused");
break;
}
return Read_result::READ_OK;
case Lwip_file_handle::LISTEN:
case Lwip_file_handle::INVALID: break;
}