diff options
author | Yonit Halperin <yhalperi@redhat.com> | 2009-10-16 00:21:43 +0200 |
---|---|---|
committer | Yaniv Kamay <ykamay@redhat.com> | 2009-10-18 17:42:37 +0200 |
commit | ef213c66c19d265140e9a55519b174d34ff1f16b (patch) | |
tree | 0a8e58217f5757881d4d4798d1316dbb3809f37a /client | |
parent | 308e4545cbf8d26d5d47ad6ab9f2c6e6e6648003 (diff) | |
download | spice-ef213c66c19d265140e9a55519b174d34ff1f16b.tar.gz spice-ef213c66c19d265140e9a55519b174d34ff1f16b.tar.xz spice-ef213c66c19d265140e9a55519b174d34ff1f16b.zip |
tunnel
Diffstat (limited to 'client')
-rw-r--r-- | client/Makefile.am | 4 | ||||
-rw-r--r-- | client/application.cpp | 10 | ||||
-rw-r--r-- | client/client_net_socket.cpp | 386 | ||||
-rw-r--r-- | client/client_net_socket.h | 154 | ||||
-rw-r--r-- | client/red_channel.cpp | 38 | ||||
-rw-r--r-- | client/red_peer.cpp | 46 | ||||
-rw-r--r-- | client/red_peer.h | 17 | ||||
-rw-r--r-- | client/tunnel_channel.cpp | 792 | ||||
-rw-r--r-- | client/tunnel_channel.h | 138 | ||||
-rw-r--r-- | client/windows/platform_utils.cpp | 17 | ||||
-rw-r--r-- | client/windows/platform_utils.h | 22 | ||||
-rw-r--r-- | client/windows/redc.vcproj | 16 | ||||
-rw-r--r-- | client/x11/Makefile.am | 6 | ||||
-rw-r--r-- | client/x11/platform_utils.h | 21 |
14 files changed, 1587 insertions, 80 deletions
diff --git a/client/Makefile.am b/client/Makefile.am index bb78ba49..51d4857e 100644 --- a/client/Makefile.am +++ b/client/Makefile.am @@ -15,6 +15,8 @@ RED_COMMON_SRCS = \ canvas_utils.cpp \ red_cairo_canvas.cpp \ red_cairo_canvas.h \ + client_net_socket.cpp \ + client_net_socket.h \ cmd_line_parser.cpp \ cmd_line_parser.h \ common.h \ @@ -71,6 +73,8 @@ RED_COMMON_SRCS = \ screen_layer.cpp \ screen_layer.h \ shared_cache.hpp \ + tunnel_channel.cpp \ + tunnel_channel.h \ hot_keys.cpp \ hot_keys.h \ threads.cpp \ diff --git a/client/application.cpp b/client/application.cpp index b30baa84..65937156 100644 --- a/client/application.cpp +++ b/client/application.cpp @@ -40,6 +40,7 @@ #include "quic.h" #include "mutex.h" #include "cmd_line_parser.h" +#include "tunnel_channel.h" #include <log4cpp/BasicConfigurator.hh> #include <log4cpp/FileAppender.hh> @@ -236,7 +237,7 @@ enum AppCommands { Application::Application() : _client (*this) - , _enabled_channels(RED_CHANNEL_END, true) + , _enabled_channels (RED_CHANNEL_END, true) , _main_screen (NULL) , _quitting (false) , _active (false) @@ -1323,6 +1324,7 @@ bool Application::set_channels_security(CmdLineParser& parser, bool on, char *va channels_names["cursor"] = RED_CHANNEL_CURSOR; channels_names["playback"] = RED_CHANNEL_PLAYBACK; channels_names["record"] = RED_CHANNEL_RECORD; + channels_names["tunnel"] = RED_CHANNEL_TUNNEL; if (!strcmp(val, "all")) { if ((val = parser.next_argument())) { @@ -1382,6 +1384,7 @@ bool Application::set_enable_channels(CmdLineParser& parser, bool enable, char * channels_names["cursor"] = RED_CHANNEL_CURSOR; channels_names["playback"] = RED_CHANNEL_PLAYBACK; channels_names["record"] = RED_CHANNEL_RECORD; + channels_names["tunnel"] = RED_CHANNEL_TUNNEL; if (!strcmp(val, "all")) { if ((val = parser.next_argument())) { @@ -1460,6 +1463,7 @@ bool Application::process_cmd_line(int argc, char** argv) _peer_con_opt[RED_CHANNEL_CURSOR] = RedPeer::ConnectionOptions::CON_OP_INVALID; _peer_con_opt[RED_CHANNEL_PLAYBACK] = RedPeer::ConnectionOptions::CON_OP_INVALID; _peer_con_opt[RED_CHANNEL_RECORD] = RedPeer::ConnectionOptions::CON_OP_INVALID; + _peer_con_opt[RED_CHANNEL_TUNNEL] = RedPeer::ConnectionOptions::CON_OP_INVALID; parser.begin(argc, argv); @@ -1595,6 +1599,10 @@ bool Application::process_cmd_line(int argc, char** argv) _client.register_channel_factory(RecordChannel::Factory()); } + if (_enabled_channels[RED_CHANNEL_TUNNEL]) { + _client.register_channel_factory(TunnelChannel::Factory()); + } + _client.init(host.c_str(), port, sport, password.c_str(), auto_display_res); if (auto_display_res) { Monitor* mon = find_monitor(0); diff --git a/client/client_net_socket.cpp b/client/client_net_socket.cpp new file mode 100644 index 00000000..50174795 --- /dev/null +++ b/client/client_net_socket.cpp @@ -0,0 +1,386 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + + Author: + yhalperi@redhat.com +*/ + +#include "common.h" +#include "client_net_socket.h" +#include "debug.h" +#include "red_error_codes.h" +#include "utils.h" + +ClientNetSocket::ClientNetSocket(uint16_t id, const struct in_addr& dst_addr, uint16_t dst_port, + EventsLoop& events_loop, EventHandler& event_handler) + : _id (id) + , _local_addr (dst_addr) + , _local_port (dst_port) + , _peer (INVALID_SOCKET) + , _events_loop (events_loop) + , _event_handler (event_handler) + , _num_recv_tokens (0) + , _send_message (NULL) + , _send_pos (0) + , _status (SOCKET_STATUS_CLOSED) + , _fin_pending (false) + , _close_pending (false) +{ +} + +ClientNetSocket::~ClientNetSocket() +{ + close(); +} + +bool ClientNetSocket::connect(uint32_t recv_tokens) +{ + struct sockaddr_in addr; + int no_delay; + + + ASSERT(_peer == INVALID_SOCKET && _status == SOCKET_STATUS_CLOSED); + + addr.sin_port = _local_port; + addr.sin_addr.s_addr = _local_addr.s_addr; + addr.sin_family = AF_INET; + + if ((_peer = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) { + int err = sock_error(); + THROW("%s: failed to create socket: %s", __FUNCTION__, sock_err_message(err)); + } + + no_delay = 1; + if (setsockopt(_peer, IPPROTO_TCP, TCP_NODELAY, + (const char*)&no_delay, sizeof(no_delay)) == SOCKET_ERROR) { + LOG_WARN("set TCP_NODELAY failed"); + } + + LOG_INFO("connect to ip=%s port=%d (connection_id=%d)", + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), _id); + + if (::connect(_peer, (struct sockaddr*)&addr, sizeof(sockaddr_in)) == SOCKET_ERROR) { + int err = sock_error(); + closesocket(_peer); + _peer = INVALID_SOCKET; + LOG_INFO("connect to ip=%s port=%d failed %s (connection_id=%d)", + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), sock_err_message(err), _id); + return false; + } + + _events_loop.add_socket(*this); + _status = SOCKET_STATUS_OPEN; + _num_recv_tokens = recv_tokens; + return true; +} + +void ClientNetSocket::push_disconnect() +{ + if ((_status == SOCKET_STATUS_CLOSED) || _close_pending) { + THROW("%s: disconnect attempt for disconnected socket %s %d", __FUNCTION__, + inet_ntoa(_local_addr), ntohs(_local_port)); + } + + _close_pending = true; + + if (!during_send()) { + close_and_tell(); + } +} + +void ClientNetSocket::push_fin() +{ + if ((_status == SOCKET_STATUS_OPEN) || (_status == SOCKET_STATUS_RECEIVED_FIN)) { + _fin_pending = true; + if (!during_send()) { + try { + apply_guest_fin(); + } catch (ClientNetSocket::ShutdownExcpetion&) { + close_and_tell(); + } + } + } else { + THROW("%s: unexpected fin connection_id=%d (status=%d)", __FUNCTION__, + _id, _status); + } +} + +void ClientNetSocket::add_recv_tokens(uint32_t num_tokens) +{ + if ((_status == SOCKET_STATUS_CLOSED) || _close_pending) { + THROW("%s: ack attempt for disconnected socket connection_id=%d", __FUNCTION__, + _id); + } + + _num_recv_tokens += num_tokens; + + // recv might have not been called because tokens weren't available + if (_num_recv_tokens && (_num_recv_tokens == num_tokens)) { + if (can_receive()) { + receive(); + } + } +} + +void ClientNetSocket::push_send(SendBuffer& buf) +{ + if (!can_send()) { + THROW("%s: unexpected send attempt for connection_id=%d (status = %d)", + __FUNCTION__, _id, _status); + } + + if (_fin_pending || _close_pending) { + THROW("%s: unexpected send attempt for connection_id=% - shutdown send pending", + __FUNCTION__, _id); + } + + _send_messages.push_back(buf.ref()); + send(); +} + +void ClientNetSocket::on_event() +{ + if (can_send()) { + send(); + } + + if (!during_send()) { + if (_close_pending) { + close_and_tell(); + } else if (_fin_pending) { + apply_guest_fin(); + } + } + + if (can_receive()) { + receive(); + } +} + +void ClientNetSocket::apply_guest_fin() +{ + if (_status == SOCKET_STATUS_OPEN) { + if (shutdown(_peer, SHUT_WR) == SOCKET_ERROR) { + int err = sock_error(); + LOG_INFO("shutdown in connection_id=%d failed %s", _id, sock_err_message(err)); + throw ClientNetSocket::ShutdownExcpetion(); + } + + _fin_pending = false; + _status = SOCKET_STATUS_SENT_FIN; + } else if (_status == SOCKET_STATUS_RECEIVED_FIN) { + close_and_tell(); + } +} + +void ClientNetSocket::handle_client_fin() +{ + if (_status == SOCKET_STATUS_OPEN) { + _status = SOCKET_STATUS_RECEIVED_FIN; + _event_handler.on_socket_fin_recv(*this); + } else if (_status == SOCKET_STATUS_SENT_FIN) { + close_and_tell(); + } +} + +inline bool ClientNetSocket::during_send() +{ + return ((!_send_messages.empty()) || _send_message); +} + +inline bool ClientNetSocket::can_send() +{ + return ((_status == SOCKET_STATUS_OPEN) || (_status == SOCKET_STATUS_RECEIVED_FIN)); +} + +inline bool ClientNetSocket::can_receive() +{ + return ((_status == SOCKET_STATUS_OPEN) || (_status == SOCKET_STATUS_SENT_FIN)); +} + +void ClientNetSocket::send_message_done() +{ + _send_message->unref(); + _send_message = NULL; + _send_pos = 0; + _event_handler.on_socket_message_send_done(*this); +} + +void ClientNetSocket::send() +{ + ASSERT(_peer != INVALID_SOCKET); + try { + if (_send_message) { + _send_pos += send_buf(_send_message->data() + _send_pos, + _send_message->size() - _send_pos); + + if (_send_pos != _send_message->size()) { + return; + } else { + send_message_done(); + } + } + + while (!_send_messages.empty()) { + _send_message = _send_messages.front(); + _send_messages.pop_front(); + _send_pos = send_buf(_send_message->data(), _send_message->size()); + if (_send_pos != _send_message->size()) { + return; + } else { + send_message_done(); + } + } + } catch (ClientNetSocket::SendException&) { + close_and_tell(); + } +} + +uint32_t ClientNetSocket::send_buf(const uint8_t* buf, uint32_t size) +{ + const uint8_t* pos = buf; + ASSERT(_peer != INVALID_SOCKET); + while (size) { + int now; + if ((now = ::send(_peer, (char*)pos, size, MSG_NOSIGNAL)) == SOCKET_ERROR) { + int err = sock_error(); + if (err == WOULDBLOCK_ERR) { + break; + } + + if (err == INTERRUPTED_ERR) { + continue; + } + + LOG_INFO("send in connection_id=%d failed %s", _id, sock_err_message(err)); + throw ClientNetSocket::SendException(); + } + size -= now; + pos += now; + } + return pos - buf; +} + +void ClientNetSocket::receive() +{ + ASSERT(_peer != INVALID_SOCKET); + bool shutdown; + while (_num_recv_tokens) { + ReceiveBuffer& rcv_buf = alloc_receive_buffer(); + uint32_t size; + + try { + size = receive_buf(rcv_buf.buf(), rcv_buf.buf_max_size(), shutdown); + } catch (ClientNetSocket::ReceiveException&) { + rcv_buf.release_buf(); + close_and_tell(); + return; + } + + if (size) { + rcv_buf.set_buf_size(size); + _num_recv_tokens--; + _event_handler.on_socket_message_recv_done(*this, rcv_buf); + } else { + rcv_buf.release_buf(); + } + + if (shutdown) { + handle_client_fin(); + return; + } + + if (size < rcv_buf.buf_max_size()) { + return; + } + } +} + +uint32_t ClientNetSocket::receive_buf(uint8_t* buf, uint32_t max_size, bool& shutdown) +{ + uint8_t* pos = buf; + ASSERT(_peer != INVALID_SOCKET); + + shutdown = false; + + while (max_size) { + int now; + if ((now = ::recv(_peer, (char*)pos, max_size, 0)) <= 0) { + if (now == 0) { + shutdown = true; + break; // a case where fin is received, but before that, there is a msg + } + + int err = sock_error(); + + if (err == WOULDBLOCK_ERR) { + break; + } + + if (err == INTERRUPTED_ERR) { + continue; + } + + LOG_INFO("receive in connection_id=%d failed errno=%s", _id, sock_err_message(err)); + throw ClientNetSocket::ReceiveException(); + } + max_size -= now; + pos += now; + } + + return (pos - buf); +} + +void ClientNetSocket::release_wait_send_messages() +{ + if (_send_message) { + _send_message->unref(); + _send_message = NULL; + _send_pos = 0; + } + + while (!_send_messages.empty()) { + _send_messages.front()->unref(); + _send_messages.pop_front(); + } +} + +void ClientNetSocket::close() +{ + release_wait_send_messages(); + apply_disconnect(); +} + +void ClientNetSocket::close_and_tell() +{ + close(); + _event_handler.on_socket_disconnect(*this); +} + +void ClientNetSocket::apply_disconnect() +{ + if (_peer != INVALID_SOCKET) { + _events_loop.remove_socket(*this); + closesocket(_peer); + _peer = INVALID_SOCKET; + LOG_INFO("closing connection_id=%d", _id); + } + _status = SOCKET_STATUS_CLOSED; + _close_pending = false; + _fin_pending = false; +} + diff --git a/client/client_net_socket.h b/client/client_net_socket.h new file mode 100644 index 00000000..8ca1cfcf --- /dev/null +++ b/client/client_net_socket.h @@ -0,0 +1,154 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + Author: + yhalperi@redhat.com +*/ + +#ifndef _H_CLIENT_NET_SOCKET +#define _H_CLIENT_NET_SOCKET + +#include "platform_utils.h" +#include "common.h" +#include "events_loop.h" + +/* intterface for connenctions inside client LAN */ + +typedef enum { + SOCKET_STATUS_OPEN, + SOCKET_STATUS_SENT_FIN, + SOCKET_STATUS_RECEIVED_FIN, + SOCKET_STATUS_CLOSED, +} SocketStatus; + +class ClientNetSocket: public EventsLoop::Socket { +public: + class ReceiveBuffer; + class SendBuffer; + + class EventHandler; + + class SendException {}; + class ReceiveException {}; + class ShutdownExcpetion {}; + + ClientNetSocket(uint16_t id, const struct in_addr& dst_addr, uint16_t dst_port, + EventsLoop& events_loop, ClientNetSocket::EventHandler& event_handler); + virtual ~ClientNetSocket(); + + bool connect(uint32_t recv_tokens); + void push_disconnect(); + void push_fin(); + void push_send(SendBuffer& buf); + void add_recv_tokens(uint32_t num_tokens); + + bool is_connected() {return _status != SOCKET_STATUS_CLOSED;} + + inline uint16_t id() {return _id;} + inline const struct in_addr& local_addr() {return _local_addr;} + inline uint16_t local_port() {return _local_port;} + + /* EventsLoop::Socket interface */ + void on_event(); + int get_socket() {return _peer;} + +protected: + virtual ReceiveBuffer& alloc_receive_buffer() = 0; + +private: + void send(); + void receive(); + + uint32_t send_buf(const uint8_t* buf, uint32_t size); + uint32_t receive_buf(uint8_t* buf, uint32_t max_size, bool& shutdown); + + bool can_receive(); + bool can_send(); + + void apply_disconnect(); + void apply_guest_fin(); + + void close(); + void close_and_tell(); + + void handle_client_fin(); + + bool during_send(); + void release_wait_send_messages(); + void clear(); + void send_message_done(); + +private: + uint16_t _id; + struct in_addr _local_addr; + uint16_t _local_port; + + SOCKET _peer; + + EventsLoop& _events_loop; + + EventHandler& _event_handler; + + uint32_t _num_recv_tokens; + + std::list<SendBuffer*> _send_messages; + SendBuffer* _send_message; + uint32_t _send_pos; + + SocketStatus _status; + bool _fin_pending; + bool _close_pending; +}; + +class ClientNetSocket::ReceiveBuffer { +public: + ReceiveBuffer() {} + + virtual uint8_t* buf() = 0; + virtual uint32_t buf_max_size() = 0; + virtual void set_buf_size(uint32_t size) = 0; + virtual void release_buf() = 0; + +protected: + virtual ~ReceiveBuffer() {} +}; + +class ClientNetSocket::SendBuffer { +public: + SendBuffer() {}; + + virtual const uint8_t* data() = 0; + virtual uint32_t size() = 0; + virtual ClientNetSocket::SendBuffer* ref() = 0; + virtual void unref() = 0; + +protected: + virtual ~SendBuffer() {} +}; + +class ClientNetSocket::EventHandler { +public: + EventHandler() {} + virtual ~EventHandler() {} + virtual void on_socket_message_recv_done(ClientNetSocket& sckt, ReceiveBuffer& buf) = 0; + virtual void on_socket_message_send_done(ClientNetSocket& sckt) = 0; + virtual void on_socket_disconnect(ClientNetSocket& sckt) = 0; + virtual void on_socket_fin_recv(ClientNetSocket& sckt) = 0; +}; + + + +#endif diff --git a/client/red_channel.cpp b/client/red_channel.cpp index 4c6f1f8f..a82d9f77 100644 --- a/client/red_channel.cpp +++ b/client/red_channel.cpp @@ -133,7 +133,7 @@ void RedChannelBase::link(uint32_t connection_id, const std::string& password) */ if (RSA_public_encrypt(password.length() + 1, (unsigned char *)password.c_str(), (uint8_t *)bufEncrypted.get(), - rsa, RSA_PKCS1_OAEP_PADDING) > 0 ) { + rsa, RSA_PKCS1_OAEP_PADDING) > 0) { send((uint8_t*)bufEncrypted.get(), nRSASize); } else { THROW("could not encrypt password"); @@ -425,8 +425,10 @@ void RedChannel::run() _outgoing_message = NULL; } _incomming_header_pos = 0; - delete _incomming_message; - _incomming_message = NULL; + if (_incomming_message) { + _incomming_message->unref(); + _incomming_message = NULL; + } case DISCONNECT_ACTION: close(); on_disconnect(); @@ -525,19 +527,19 @@ void RedChannel::recive_messages() _incomming_header_pos = n; return; } - std::auto_ptr<CompundInMessage> message(new CompundInMessage(_incomming_header.serial, - _incomming_header.type, - _incomming_header.size, - _incomming_header.sub_list)); - n = RedPeer::recive(message->data(), message->compund_size()); - if (n != message->compund_size()) { + AutoRef<CompundInMessage> message(new CompundInMessage(_incomming_header.serial, + _incomming_header.type, + _incomming_header.size, + _incomming_header.sub_list)); + n = RedPeer::recive((*message)->data(), (*message)->compund_size()); + if (n != (*message)->compund_size()) { _incomming_message = message.release(); _incomming_message_pos = n; return; } on_message_recived(); - _message_handler->handle_message(*message.get()); - on_message_complition(message->serial()); + _message_handler->handle_message(*(*message)); + on_message_complition((*message)->serial()); } } @@ -577,11 +579,11 @@ void RedChannel::on_event() if (_incomming_message_pos != _incomming_message->compund_size()) { return; } - std::auto_ptr<CompundInMessage> message(_incomming_message); + AutoRef<CompundInMessage> message(_incomming_message); _incomming_message = NULL; on_message_recived(); - _message_handler->handle_message(*message.get()); - on_message_complition(message->serial()); + _message_handler->handle_message(*(*message)); + on_message_complition((*message)->serial()); } recive_messages(); } @@ -616,18 +618,18 @@ void RedChannel::handle_migrate(RedPeer::InMessage* message) if (migrate->flags & RED_MIGRATE_NEED_FLUSH) { send_migrate_flush_mark(); } - std::auto_ptr<RedPeer::CompundInMessage> data_message; + AutoRef<CompundInMessage> data_message; if (migrate->flags & RED_MIGRATE_NEED_DATA_TRANSFER) { data_message.reset(recive()); } _client.migrate_channel(*this); if (migrate->flags & RED_MIGRATE_NEED_DATA_TRANSFER) { - if (data_message->type() != RED_MIGRATE_DATA) { + if ((*data_message)->type() != RED_MIGRATE_DATA) { THROW("expect RED_MIGRATE_DATA"); } std::auto_ptr<RedPeer::OutMessage> message(new RedPeer::OutMessage(REDC_MIGRATE_DATA, - data_message->size())); - memcpy(message->data(), data_message->data(), data_message->size()); + (*data_message)->size())); + memcpy(message->data(), (*data_message)->data(), (*data_message)->size()); send(*message); } _loop.add_socket(*this); diff --git a/client/red_peer.cpp b/client/red_peer.cpp index e20d5ca6..dad035d4 100644 --- a/client/red_peer.cpp +++ b/client/red_peer.cpp @@ -16,54 +16,12 @@ */ #include "common.h" -#ifdef _WIN32 -#include <winsock2.h> -#include <ws2tcpip.h> - -#define SHUT_RDWR SD_BOTH -#else -#include <sys/socket.h> -#include <netinet/in.h> -#include <arpa/inet.h> -#include <netinet/tcp.h> -#include <netdb.h> - -#define INVALID_SOCKET -1 -#define SOCKET_ERROR -1 -#define closesocket(sock) ::close(sock) -#endif #include "red.h" #include "red_peer.h" #include "utils.h" #include "debug.h" #include "platform_utils.h" -#ifdef _WIN32 - -int inet_aton(const char *ip, struct in_addr *in_addr) -{ - unsigned long addr = inet_addr(ip); - - if (addr == INADDR_NONE) { - return 0; - } - in_addr->S_un.S_addr = addr; - return 1; -} - -#define SHUTDOWN_ERR WSAESHUTDOWN -#define INTERRUPTED_ERR WSAEINTR -#define WOULDBLOCK_ERR WSAEWOULDBLOCK -#define sock_error() WSAGetLastError() -#define sock_err_message(err) sys_err_to_str(err) -#else -#define SHUTDOWN_ERR EPIPE -#define INTERRUPTED_ERR EINTR -#define WOULDBLOCK_ERR EAGAIN -#define sock_error() errno -#define sock_err_message(err) strerror(err) -#endif - static void ssl_error() { ERR_print_errors_fp(stderr); @@ -326,11 +284,11 @@ uint32_t RedPeer::recive(uint8_t *buf, uint32_t size) RedPeer::CompundInMessage* RedPeer::recive() { RedDataHeader header; - std::auto_ptr<CompundInMessage> message; + AutoRef<CompundInMessage> message; recive((uint8_t*)&header, sizeof(RedDataHeader)); message.reset(new CompundInMessage(header.serial, header.type, header.size, header.sub_list)); - recive(message->data(), message->compund_size()); + recive((*message)->data(), (*message)->compund_size()); return message.release(); } diff --git a/client/red_peer.h b/client/red_peer.h index f1db1814..eb0597d8 100644 --- a/client/red_peer.h +++ b/client/red_peer.h @@ -18,12 +18,6 @@ #ifndef _H_REDPEER #define _H_REDPEER -#ifdef _WIN32 -#include <winsock.h> -#else -typedef int SOCKET; -#endif - #include <openssl/ssl.h> #include <openssl/err.h> @@ -31,6 +25,7 @@ typedef int SOCKET; #include "red.h" #include "events_loop.h" #include "threads.h" +#include "platform_utils.h" class RedPeer: protected EventsLoop::Socket { public: @@ -116,7 +111,7 @@ private: class RedPeer::InMessage { public: - InMessage(uint16_t type, uint32_t size, uint8_t* data) + InMessage(uint16_t type, uint32_t size, uint8_t * data) : _type (type) , _size (size) , _data (data) @@ -139,12 +134,14 @@ class RedPeer::CompundInMessage: public RedPeer::InMessage { public: CompundInMessage(uint64_t _serial, uint16_t type, uint32_t size, uint32_t sub_list) : InMessage(type, size, new uint8_t[size]) + , _refs (1) , _serial (_serial) , _sub_list (sub_list) { } - virtual ~CompundInMessage() { delete[] _data;} + RedPeer::InMessage* ref() { _refs++; return this;} + void unref() {if (!--_refs) delete this;} uint64_t serial() { return _serial;} uint32_t sub_list() { return _sub_list;} @@ -152,7 +149,11 @@ public: virtual uint32_t size() { return _sub_list ? _sub_list : _size;} uint32_t compund_size() {return _size;} +protected: + virtual ~CompundInMessage() { delete[] _data;} + private: + int _refs; uint64_t _serial; uint32_t _sub_list; }; diff --git a/client/tunnel_channel.cpp b/client/tunnel_channel.cpp new file mode 100644 index 00000000..1d28ee48 --- /dev/null +++ b/client/tunnel_channel.cpp @@ -0,0 +1,792 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + + Author: + yhalperi@redhat.com +*/ + +#include "common.h" +#include "tunnel_channel.h" +#include "red.h" + +#define SOCKET_WINDOW_SIZE 60 +#define SOCKET_TOKENS_TO_SEND 20 + +/* classes for tunneling msgs without reallocations and memcpy */ + +class InSocketMessage; +class OutSocketMessage; + +class InSocketMessage: public ClientNetSocket::SendBuffer { +public: + InSocketMessage(RedChannel::CompundInMessage& full_msg); + + const uint8_t* data(); + uint32_t size(); + ClientNetSocket::SendBuffer* ref(); + void unref(); + +protected: + virtual ~InSocketMessage() {} + +private: + int _refs; + RedChannel::CompundInMessage& _full_msg; + RedTunnelSocketData* _sckt_msg; + uint32_t _buf_size; +}; + +InSocketMessage::InSocketMessage(RedChannel::CompundInMessage& full_msg) + : _refs (1) + , _full_msg (full_msg) +{ + ASSERT(full_msg.type() == RED_TUNNEL_SOCKET_DATA); + _full_msg.ref(); + _sckt_msg = (RedTunnelSocketData*)(_full_msg.data()); + _buf_size = _full_msg.size() - sizeof(RedTunnelSocketData); +} + +const uint8_t* InSocketMessage::data() +{ + return _sckt_msg->data; +} + +uint32_t InSocketMessage::size() +{ + return _buf_size; +} + +ClientNetSocket::SendBuffer* InSocketMessage::ref() +{ + _full_msg.ref(); + _refs++; + return this; +} + +void InSocketMessage::unref() +{ + _full_msg.unref(); + if (!--_refs) { + delete this; + } +} + +class OutSocketMessage: public RedPeer::OutMessage, + public RedChannel::OutMessage, + public ClientNetSocket::ReceiveBuffer { +public: + + virtual RedPeer::OutMessage& peer_message() { return *this;} + virtual void release(); + + virtual uint8_t* buf(); + virtual uint32_t buf_max_size() {return _max_data_size;} + virtual void set_buf_size(uint32_t size); + virtual void release_buf(); + + static void init(uint32_t max_data_size); + static OutSocketMessage& alloc_message(); + static void clear_free_messages(); + +protected: + OutSocketMessage(); + virtual ~OutSocketMessage() {} + +private: + static std::list<OutSocketMessage*> _free_messages; + static uint32_t _max_data_size; +}; + +std::list<OutSocketMessage*> OutSocketMessage::_free_messages; +uint32_t OutSocketMessage::_max_data_size; + +OutSocketMessage::OutSocketMessage() + : RedPeer::OutMessage(REDC_TUNNEL_SOCKET_DATA, sizeof(RedcTunnelSocketData) + _max_data_size) + , RedChannel::OutMessage() + , ClientNetSocket::ReceiveBuffer() +{ +} + +uint8_t* OutSocketMessage::buf() +{ + return ((RedcTunnelSocketData*)RedPeer::OutMessage::data())->data; +} + +void OutSocketMessage::set_buf_size(uint32_t size) +{ + RedPeer::OutMessage::header().size = size + sizeof(RedcTunnelSocketData); +} + +void OutSocketMessage::release() +{ + OutSocketMessage::_free_messages.push_front(this); +} + +void OutSocketMessage::release_buf() +{ + release(); +} + +void OutSocketMessage::init(uint32_t max_data_size) +{ + _max_data_size = max_data_size; +} + +OutSocketMessage& OutSocketMessage::alloc_message() +{ + OutSocketMessage* ret; + if (!_free_messages.empty()) { + ret = _free_messages.front(); + _free_messages.pop_front(); + } else { + ret = new OutSocketMessage(); + } + + return *ret; +} + +void OutSocketMessage::clear_free_messages() +{ + while (!_free_messages.empty()) { + OutSocketMessage* message = _free_messages.front(); + _free_messages.pop_front(); + delete message; + } +} + +struct TunnelService { + uint32_t type; + uint32_t id; + uint32_t group; + struct in_addr ip; + uint32_t port; + std::string name; + std::string description; + + struct in_addr virtual_ip; +#ifdef TUNNEL_CONFIG + TunnelConfigConnectionIfc* service_src; +#endif +}; + +class TunnelChannel::TunnelSocket: public ClientNetSocket { +public: + TunnelSocket(uint16_t id, TunnelService & dst_service, EventsLoop & events_loop, + EventHandler & event_handler); + virtual ~TunnelSocket() {} + + void set_num_tokens(uint32_t tokens) {_num_tokens = tokens;} + void set_server_num_tokens(uint32_t tokens) {_server_num_tokens = tokens;} + void set_guest_closed() {_guest_closed = true;} + + uint32_t get_num_tokens() {return _num_tokens;} + uint32_t get_server_num_tokens() {return _server_num_tokens;} + bool get_guest_closed() {return _guest_closed;} + +protected: + virtual ReceiveBuffer& alloc_receive_buffer() {return OutSocketMessage::alloc_message();} + +private: + uint32_t _num_tokens; + uint32_t _server_num_tokens; + uint32_t _service_id; + bool _guest_closed; +}; + +TunnelChannel::TunnelSocket::TunnelSocket(uint16_t id, TunnelService& dst_service, + EventsLoop& events_loop, + ClientNetSocket::EventHandler& event_handler) + : ClientNetSocket(id, dst_service.ip, htons((uint16_t)dst_service.port), + events_loop, event_handler) + , _num_tokens (0) + , _server_num_tokens (0) + , _service_id (dst_service.id) + , _guest_closed (false) +{ +} + +class TunnelHandler: public MessageHandlerImp<TunnelChannel, RED_TUNNEL_MESSAGES_END> { +public: + TunnelHandler(TunnelChannel& channel) + : MessageHandlerImp<TunnelChannel, RED_TUNNEL_MESSAGES_END>(channel) {} +}; + +TunnelChannel::TunnelChannel(RedClient& client, uint32_t id) + : RedChannel(client, RED_CHANNEL_TUNNEL, id, new TunnelHandler(*this)) + , _max_socket_data_size(0) + , _service_id(0) + , _service_group(0) +{ + TunnelHandler* handler = static_cast<TunnelHandler*>(get_message_handler()); + + handler->set_handler(RED_MIGRATE, &TunnelChannel::handle_migrate, 0); + handler->set_handler(RED_SET_ACK, &TunnelChannel::handle_set_ack, sizeof(RedSetAck)); + handler->set_handler(RED_PING, &TunnelChannel::handle_ping, sizeof(RedPing)); + handler->set_handler(RED_WAIT_FOR_CHANNELS, &TunnelChannel::handle_wait_for_channels, + sizeof(RedWaitForChannels)); + + handler->set_handler(RED_TUNNEL_INIT, + &TunnelChannel::handle_init, sizeof(RedTunnelInit)); + handler->set_handler(RED_TUNNEL_SERVICE_IP_MAP, + &TunnelChannel::handle_service_ip_map, sizeof(RedTunnelServiceIpMap)); + handler->set_handler(RED_TUNNEL_SOCKET_OPEN, + &TunnelChannel::handle_socket_open, sizeof(RedTunnelSocketOpen)); + handler->set_handler(RED_TUNNEL_SOCKET_CLOSE, + &TunnelChannel::handle_socket_close, sizeof(RedTunnelSocketClose)); + handler->set_handler(RED_TUNNEL_SOCKET_FIN, + &TunnelChannel::handle_socket_fin, sizeof(RedTunnelSocketFin)); + handler->set_handler(RED_TUNNEL_SOCKET_TOKEN, + &TunnelChannel::handle_socket_token, sizeof(RedTunnelSocketTokens)); + handler->set_handler(RED_TUNNEL_SOCKET_CLOSED_ACK, + &TunnelChannel::handle_socket_closed_ack, + sizeof(RedTunnelSocketClosedAck)); + handler->set_handler(RED_TUNNEL_SOCKET_DATA, + &TunnelChannel::handle_socket_data, sizeof(RedTunnelSocketData)); +#ifdef TUNNEL_CONFIG + _config_listener = new TunnelConfigListenerIfc(*this); +#endif +} + +TunnelChannel::~TunnelChannel() +{ + destroy_sockets(); + OutSocketMessage::clear_free_messages(); +#ifdef TUNNEL_CONFIG + delete _config_listener; +#endif +} + +void TunnelChannel::handle_init(RedPeer::InMessage* message) +{ + RedTunnelInit* init_msg = (RedTunnelInit*)message->data(); + _max_socket_data_size = init_msg->max_socket_data_size; + OutSocketMessage::init(_max_socket_data_size); + _sockets.resize(init_msg->max_num_of_sockets); +} + +void TunnelChannel::send_service(TunnelService& service) +{ + int msg_size = 0; + msg_size += service.name.length() + 1; + msg_size += service.description.length() + 1; + + if (service.type == RED_TUNNEL_SERVICE_TYPE_IPP) { + msg_size += sizeof(RedcTunnelAddPrintService) + sizeof(RedTunnelIPv4); + } else if (service.type == RED_TUNNEL_SERVICE_TYPE_GENERIC) { + msg_size += sizeof(RedcTunnelAddGenericService); + } else { + THROW("%s: invalid service type", __FUNCTION__); + } + Message* service_msg = new Message(REDC_TUNNEL_SERVICE_ADD, msg_size); + RedcTunnelAddGenericService* out_service = (RedcTunnelAddGenericService*)service_msg->data(); + out_service->id = service.id; + out_service->group = service.group; + out_service->type = service.type; + out_service->port = service.port; + + int cur_offset; + if (service.type == RED_TUNNEL_SERVICE_TYPE_IPP) { + cur_offset = sizeof(RedcTunnelAddPrintService); + ((RedcTunnelAddPrintService*)out_service)->ip.type = RED_TUNNEL_IP_TYPE_IPv4; + memcpy(((RedcTunnelAddPrintService*)out_service)->ip.data, &(service.ip.s_addr), + sizeof(RedTunnelIPv4)); + cur_offset += sizeof(RedTunnelIPv4); + } else { + cur_offset = sizeof(RedcTunnelAddGenericService); + } + + out_service->name = cur_offset; + service.name.copy((char*)(service_msg->data() + cur_offset), service.name.length()); + (service_msg->data() + cur_offset)[service.name.length()] = '\0'; + cur_offset += service.name.length() + 1; + + out_service->description = cur_offset; + service.description.copy((char*)(service_msg->data() + cur_offset), + service.description.length()); + (service_msg->data() + cur_offset)[service.description.length()] = '\0'; + cur_offset += service.description.length() + 1; + + post_message(service_msg); +} + +void TunnelChannel::handle_service_ip_map(RedPeer::InMessage* message) +{ + RedTunnelServiceIpMap* service_ip_msg = (RedTunnelServiceIpMap*)message->data(); + TunnelService* service = find_service(service_ip_msg->service_id); + if (!service) { + THROW("%s: attempt to map non-existing service id=%d", __FUNCTION__, + service_ip_msg->service_id); + } + + if (service_ip_msg->virtual_ip.type == RED_TUNNEL_IP_TYPE_IPv4) { + memcpy(&service->virtual_ip.s_addr, service_ip_msg->virtual_ip.data, + sizeof(RedTunnelIPv4)); + } else { + THROW("unexpected ip type %d", service_ip_msg->virtual_ip.type); + } + DBG(0, "service_id=%d (%s), virtual_ip=%s", service->id, service->name.c_str(), + inet_ntoa(service->virtual_ip)); +#ifdef TUNNEL_CONFIG + service->service_src->send_virtual_ip(service->virtual_ip); +#endif +} + +void TunnelChannel::handle_socket_open(RedPeer::InMessage* message) +{ + RedTunnelSocketOpen* open_msg = (RedTunnelSocketOpen*)message->data(); + TunnelSocket* sckt; + Message* out_msg; + + if (_sockets[open_msg->connection_id]) { + THROW("%s: attempt to open an already opened connection id=%d", __FUNCTION__, + open_msg->connection_id); + } + + TunnelService* service = find_service(open_msg->service_id); + if (!service) { + THROW("%s: attempt to access non-existing service id=%d", __FUNCTION__, + open_msg->service_id); + } + + sckt = new TunnelSocket(open_msg->connection_id, *service, get_events_loop(), *this); + + if (sckt->connect(open_msg->tokens)) { + _sockets[open_msg->connection_id] = sckt; + out_msg = new Message(REDC_TUNNEL_SOCKET_OPEN_ACK, sizeof(RedcTunnelSocketOpenAck)); + sckt->set_num_tokens(0); + sckt->set_server_num_tokens(SOCKET_WINDOW_SIZE); + + ((RedcTunnelSocketOpenAck*)out_msg->data())->connection_id = open_msg->connection_id; + ((RedcTunnelSocketOpenAck*)out_msg->data())->tokens = SOCKET_WINDOW_SIZE; + } else { + out_msg = new Message(REDC_TUNNEL_SOCKET_OPEN_NACK, sizeof(RedcTunnelSocketOpenNack)); + ((RedcTunnelSocketOpenNack*)out_msg->data())->connection_id = open_msg->connection_id; + delete sckt; + } + + post_message(out_msg); +} + +void TunnelChannel::handle_socket_fin(RedPeer::InMessage* message) +{ + RedTunnelSocketFin* fin_msg = (RedTunnelSocketFin*)message->data(); + TunnelSocket* sckt = _sockets[fin_msg->connection_id]; + + if (!sckt) { + THROW("%s: fin connection that doesn't exist id=%d", __FUNCTION__, fin_msg->connection_id); + } + + DBG(0, "guest fin connection_id=%d", fin_msg->connection_id); + if (sckt->is_connected()) { + sckt->push_fin(); + } +} + +void TunnelChannel::handle_socket_close(RedPeer::InMessage* message) +{ + RedTunnelSocketClose* close_msg = (RedTunnelSocketClose*)message->data(); + TunnelSocket* sckt = _sockets[close_msg->connection_id]; + + if (!sckt) { + THROW("%s: closing connection that doesn't exist id=%d", __FUNCTION__, + close_msg->connection_id); + } + DBG(0, "guest closed connection_id=%d", close_msg->connection_id); + + sckt->set_guest_closed(); + + if (sckt->is_connected()) { + sckt->push_disconnect(); + } else { + // close happend in the server side before it received the client + // close msg. we should ack the server and free the socket + on_socket_disconnect(*sckt); + } +} + +void TunnelChannel::handle_socket_closed_ack(RedPeer::InMessage* message) +{ + RedTunnelSocketClosedAck* close_ack_msg = (RedTunnelSocketClosedAck*)message->data(); + TunnelSocket* sckt = _sockets[close_ack_msg->connection_id]; + if (!sckt) { + THROW("%s: close ack to connection that doesn't exist id=%d", __FUNCTION__, + close_ack_msg->connection_id); + } + + if (sckt->is_connected()) { + THROW("%s: close ack to connection that is not closed id=%d", + __FUNCTION__, close_ack_msg->connection_id); + } + _sockets[sckt->id()] = NULL; + DBG(0, "guest Acked closed connection_id=%d", close_ack_msg->connection_id); + delete sckt; +} + +void TunnelChannel::handle_socket_data(RedPeer::InMessage* message) +{ + RedTunnelSocketData* send_msg = (RedTunnelSocketData*)message->data(); + TunnelSocket* sckt = _sockets[send_msg->connection_id]; + + if (!sckt) { + THROW("%s: sending data to connection that doesn't exist id=%d", __FUNCTION__, + send_msg->connection_id); + } + + if (!sckt->get_server_num_tokens()) { + THROW("%s: token violation connectio_id=%d", __FUNCTION__, sckt->id()); + } + + sckt->set_server_num_tokens(sckt->get_server_num_tokens() - 1); + + if (!sckt->is_connected()) { + // server hasn't handled the close msg yet + return; + } + + InSocketMessage* sckt_msg = new InSocketMessage(*( + static_cast<RedChannel::CompundInMessage*>(message))); + if (sckt_msg->size() > _max_socket_data_size) { + THROW("%s: socket data exceeds size limit %d > %d connection_id=%d", __FUNCTION__, + sckt_msg->size(), _max_socket_data_size, sckt->id()); + } + sckt->push_send(*sckt_msg); + sckt_msg->unref(); +} + +void TunnelChannel::handle_socket_token(RedPeer::InMessage* message) +{ + RedTunnelSocketTokens* token_msg = (RedTunnelSocketTokens*)message->data(); + TunnelSocket* sckt = _sockets[token_msg->connection_id]; + + if (!sckt) { + THROW("%s: ack connection that doesn't exist id=%d", __FUNCTION__, + token_msg->connection_id); + } + if (!sckt->is_connected()) { + return; + } + sckt->add_recv_tokens(token_msg->num_tokens); +} + +void TunnelChannel::on_socket_message_recv_done(ClientNetSocket& sckt, + ClientNetSocket::ReceiveBuffer& buf) +{ + TunnelChannel::TunnelSocket* tunnel_sckt = static_cast<TunnelChannel::TunnelSocket*>(&sckt); + OutSocketMessage* out_msg = static_cast<OutSocketMessage*>(&buf); + + ((RedcTunnelSocketData*)(out_msg->data()))->connection_id = tunnel_sckt->id(); + post_message(out_msg); +} + +void TunnelChannel::on_socket_fin_recv(ClientNetSocket& sckt) +{ + TunnelChannel::TunnelSocket* tunnel_sckt = static_cast<TunnelChannel::TunnelSocket*>(&sckt); + Message* out_msg = new Message(REDC_TUNNEL_SOCKET_FIN, sizeof(RedcTunnelSocketFin)); + DBG(0, "FIN from client coonection id=%d", tunnel_sckt->id()); + ((RedcTunnelSocketFin*)out_msg->data())->connection_id = tunnel_sckt->id(); + post_message(out_msg); +} + +void TunnelChannel::on_socket_disconnect(ClientNetSocket& sckt) +{ + TunnelChannel::TunnelSocket* tunnel_sckt = static_cast<TunnelChannel::TunnelSocket*>(&sckt); + Message* out_msg; + // close intiated by server -> needs ack + if (tunnel_sckt->get_guest_closed()) { + DBG(0, "send close ack connection_id=%d", tunnel_sckt->id()); + out_msg = new Message(REDC_TUNNEL_SOCKET_CLOSED_ACK, sizeof(RedcTunnelSocketClosedAck)); + ((RedcTunnelSocketClosedAck*)out_msg->data())->connection_id = tunnel_sckt->id(); + _sockets[tunnel_sckt->id()] = NULL; + delete &sckt; + } else { // close initiated by client + DBG(0, "send close coonection_id=%d", tunnel_sckt->id()); + out_msg = new Message(REDC_TUNNEL_SOCKET_CLOSED, sizeof(RedcTunnelSocketClosed)); + ((RedcTunnelSocketClosed*)out_msg->data())->connection_id = tunnel_sckt->id(); + } + + post_message(out_msg); +} + +void TunnelChannel::on_socket_message_send_done(ClientNetSocket& sckt) +{ + TunnelChannel::TunnelSocket* tunnel_sckt = static_cast<TunnelChannel::TunnelSocket*>(&sckt); + uint32_t num_tokens = tunnel_sckt->get_num_tokens(); + num_tokens++; + + if (num_tokens == SOCKET_TOKENS_TO_SEND) { + Message* out_msg = new Message(REDC_TUNNEL_SOCKET_TOKEN, sizeof(RedcTunnelSocketTokens)); + RedcTunnelSocketTokens* tokens_msg = (RedcTunnelSocketTokens*)out_msg->data(); + tokens_msg->connection_id = tunnel_sckt->id(); + tokens_msg->num_tokens = num_tokens; + post_message(out_msg); + + tunnel_sckt->set_num_tokens(0); + tunnel_sckt->set_server_num_tokens(tunnel_sckt->get_server_num_tokens() + num_tokens); + + ASSERT(tunnel_sckt->get_server_num_tokens() <= SOCKET_WINDOW_SIZE); + } else { + tunnel_sckt->set_num_tokens(num_tokens); + } +} + +TunnelService* TunnelChannel::find_service(uint32_t id) +{ + for (std::list<TunnelService*>::iterator iter = _services.begin(); + iter != _services.end(); iter++) { + if ((*iter)->id == id) { + return *iter; + } + } + return NULL; +} + +/* returns the first service with the same ip */ +TunnelService* TunnelChannel::find_service(struct in_addr& ip) +{ + for (std::list<TunnelService*>::iterator iter = _services.begin(); + iter != _services.end(); iter++) { + if ((*iter)->ip.s_addr == ip.s_addr) { + return *iter; + } + } + return NULL; +} + +TunnelService* TunnelChannel::find_service(struct in_addr& ip, uint32_t port) +{ + for (std::list<TunnelService*>::iterator iter = _services.begin(); + iter != _services.end(); iter++) { + if (((*iter)->ip.s_addr == ip.s_addr) && ((*iter)->port == port)) { + return *iter; + } + } + return NULL; +} + +void TunnelChannel::destroy_sockets() +{ + for (unsigned int i = 0; i < _sockets.size(); i++) { + if (_sockets[i]) { + delete _sockets[i]; + _sockets[i] = NULL; + } + } +} + +void TunnelChannel::on_disconnect() +{ + destroy_sockets(); + OutSocketMessage::clear_free_messages(); +} + +#ifdef TUNNEL_CONFIG +void TunnelChannel::add_service(TunnelConfigConnectionIfc& source, + uint32_t type, struct in_addr& ip, uint32_t port, + std::string& name, std::string& description) +{ + if (find_service(ip, port)) { + LOG_WARN("service ip=%s port=%d was already added", + inet_ntoa(ip), port); + return; + } + TunnelService* new_service = new TunnelService; + TunnelService* service_group = find_service(ip); + new_service->type = type; + new_service->id = _service_id++; + if (service_group) { + if (name != service_group->name) { + LOG_WARN("service ip=%s port=%d was not added because of inconsistent name for ip", + inet_ntoa(ip), port); + delete new_service; + return; + } + new_service->group = service_group->group; + } else { + new_service->group = _service_group++; + } + new_service->ip.s_addr = ip.s_addr; + new_service->port = port; + new_service->name = name; + new_service->description = description; + new_service->service_src = &source; + _services.push_back(new_service); + send_service(*new_service); +} + +#endif + +class TunnelFactory: public ChannelFactory { +public: + TunnelFactory() : ChannelFactory(RED_CHANNEL_TUNNEL) {} + virtual RedChannel* construct(RedClient& client, uint32_t id) + { + return new TunnelChannel(client, id); + } +}; + +static TunnelFactory factory; + +ChannelFactory& TunnelChannel::Factory() +{ + return factory; +} + +#ifdef TUNNEL_CONFIG +TunnelConfigListenerIfc::TunnelConfigListenerIfc(TunnelChannel& tunnel) + : _tunnel(tunnel) +{ + _listener_ref = NamedPipe::create(TUNNEL_CONFIG_PIPE_NAME, *this); +} + +TunnelConfigListenerIfc::~TunnelConfigListenerIfc() +{ + for (std::list<TunnelConfigConnectionIfc*>::iterator it = _connections.begin(); + it != _connections.end(); ++it) { + if ((*it)->get_ref() != NamedPipe::INVALID_CONNECTION) { + NamedPipe::destroy_connection((*it)->get_ref()); + } + delete (*it); + } + + NamedPipe::destroy(_listener_ref); +} + +NamedPipe::ConnectionInterface& TunnelConfigListenerIfc::create() +{ + DBG(0, "new_connection"); + TunnelConfigConnectionIfc* new_conn = new TunnelConfigConnectionIfc(_tunnel, *this); + _connections.push_back(new_conn); + return *new_conn; +} + +void TunnelConfigListenerIfc::destroy_connection(TunnelConfigConnectionIfc* conn) +{ + if (conn->get_ref() != NamedPipe::INVALID_CONNECTION) { + NamedPipe::destroy_connection(conn->get_ref()); + } + _connections.remove(conn); + delete conn; +} + +TunnelConfigConnectionIfc::TunnelConfigConnectionIfc(TunnelChannel& tunnel, + TunnelConfigListenerIfc& listener) + : _tunnel(tunnel) + , _listener(listener) + , _in_msg_len(0) + , _out_msg("") + , _out_msg_pos(0) +{ +} + +void TunnelConfigConnectionIfc::bind(NamedPipe::ConnectionRef conn_ref) +{ + _opaque = conn_ref; + on_data(); +} + +void TunnelConfigConnectionIfc::on_data() +{ + if (!_out_msg.empty()) { + int ret = NamedPipe::write(_opaque, (uint8_t*)_out_msg.c_str() + _out_msg_pos, + _out_msg.length() - _out_msg_pos); + if (ret == -1) { + _listener.destroy_connection(this); + return; + } + _out_msg_pos += ret; + if (_out_msg_pos == _out_msg.length()) { + _out_msg = ""; + _out_msg_pos = 0; + } + } else { + int ret = NamedPipe::read(_opaque, (uint8_t*)_in_msg + _in_msg_len, + TUNNEL_CONFIG_MAX_MSG_LEN - _in_msg_len); + + if (ret == -1) { + _listener.destroy_connection(this); + return; + } + _in_msg_len += ret; + + if (_in_msg[_in_msg_len - 1] != '\n') { + return; + } + handle_msg(); + _in_msg_len = 0; + } +} + +void TunnelConfigConnectionIfc::send_virtual_ip(struct in_addr& ip) +{ + _out_msg = inet_ntoa(ip); + _out_msg += "\n"; + _out_msg_pos = 0; + on_data(); +} + +void TunnelConfigConnectionIfc::handle_msg() +{ + std::string space = " \t"; + _in_msg[_in_msg_len - 1] = '\0'; + std::string msg(_in_msg); + + uint32_t service_type; + struct in_addr ip; + uint32_t port; + std::string name; + std::string desc; + + DBG(0, "msg=%s", _in_msg); + size_t start_token = 0; + size_t end_token; + + start_token = msg.find_first_not_of(space); + end_token = msg.find_first_of(space, start_token); + + if ((end_token - start_token) != 1) { + THROW("unexpected service type length"); + } + if (msg[start_token] == '0') { + service_type = RED_TUNNEL_SERVICE_TYPE_GENERIC; + } else if (msg[start_token] == '1') { + service_type = RED_TUNNEL_SERVICE_TYPE_IPP; + } else { + THROW("unexpected service type"); + } + + start_token = msg.find_first_not_of(space, end_token); + end_token = msg.find_first_of(space, start_token); + + inet_aton(msg.substr(start_token, end_token - start_token).c_str(), &ip); + + start_token = msg.find_first_not_of(space, end_token); + end_token = msg.find_first_of(space, start_token); + + port = atoi(msg.substr(start_token, end_token - start_token).c_str()); + + start_token = msg.find_first_not_of(space, end_token); + end_token = msg.find_first_of(space, start_token); + + name = msg.substr(start_token, end_token - start_token); + + start_token = msg.find_first_not_of(space, end_token); + desc = msg.substr(start_token); + + _tunnel.add_service(*this, service_type, ip, port, name, desc); +} + +#endif diff --git a/client/tunnel_channel.h b/client/tunnel_channel.h new file mode 100644 index 00000000..4fd3465c --- /dev/null +++ b/client/tunnel_channel.h @@ -0,0 +1,138 @@ +/* + Copyright (C) 2009 Red Hat, Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + + Author: + yhalperi@redhat.com +*/ + +#ifndef _H_TUNNEL_CHANNEL +#define _H_TUNNEL_CHANNEL + +#include "common.h" +#include "red_channel.h" +#include "red_client.h" +#include "client_net_socket.h" +#include "platform.h" + +#define TUNNEL_CONFIG + +#ifdef TUNNEL_CONFIG +class TunnelConfigConnectionIfc; +class TunnelConfigListenerIfc; +#endif + +/* channel for tunneling tcp from guest to client network */ +typedef struct TunnelService TunnelService; +class TunnelChannel: public RedChannel, + public ClientNetSocket::EventHandler { +public: + + TunnelChannel(RedClient& client, uint32_t id); + virtual ~TunnelChannel(); + + virtual void on_socket_message_recv_done(ClientNetSocket& sckt, + ClientNetSocket::ReceiveBuffer& buf); + virtual void on_socket_message_send_done(ClientNetSocket& sckt); + virtual void on_socket_fin_recv(ClientNetSocket& sckt); + virtual void on_socket_disconnect(ClientNetSocket& sckt); + +#ifdef TUNNEL_CONFIG + void add_service(TunnelConfigConnectionIfc& source, + uint32_t type, struct in_addr& ip, uint32_t port, + std::string& name, std::string& description); +#endif + static ChannelFactory& Factory(); + +protected: + class TunnelSocket; + + virtual void on_disconnect(); + +private: + void handle_init(RedPeer::InMessage* message); + void handle_service_ip_map(RedPeer::InMessage* message); + + void handle_socket_open(RedPeer::InMessage* message); + void handle_socket_fin(RedPeer::InMessage* message); + void handle_socket_close(RedPeer::InMessage* message); + void handle_socket_closed_ack(RedPeer::InMessage* message); + void handle_socket_data(RedPeer::InMessage* message); + void handle_socket_token(RedPeer::InMessage* message); + + TunnelService* find_service(uint32_t id); + TunnelService* find_service(struct in_addr& ip); + TunnelService* find_service(struct in_addr& ip, uint32_t port); + + void send_service(TunnelService& service); + void destroy_sockets(); + +private: + std::vector<TunnelSocket*> _sockets; + std::list<TunnelService*> _services; + uint32_t _max_socket_data_size; + uint32_t _service_id; + uint32_t _service_group; +#ifdef TUNNEL_CONFIG + TunnelConfigListenerIfc* _config_listener; +#endif +}; + +#ifdef TUNNEL_CONFIG +#ifdef _WIN32 +#define TUNNEL_CONFIG_PIPE_NAME "tunnel-config.pipe" +#else +#define TUNNEL_CONFIG_PIPE_NAME "/tmp/tunnel-config.pipe" +#endif + +class TunnelConfigConnectionIfc; + +class TunnelConfigListenerIfc: public NamedPipe::ListenerInterface { +public: + TunnelConfigListenerIfc(TunnelChannel& tunnel); + virtual ~TunnelConfigListenerIfc(); + virtual NamedPipe::ConnectionInterface& create(); + virtual void destroy_connection(TunnelConfigConnectionIfc* conn); + +private: + TunnelChannel& _tunnel; + NamedPipe::ListenerRef _listener_ref; + std::list<TunnelConfigConnectionIfc*> _connections; +}; + +#define TUNNEL_CONFIG_MAX_MSG_LEN 2048 +class TunnelConfigConnectionIfc: public NamedPipe::ConnectionInterface { +public: + TunnelConfigConnectionIfc(TunnelChannel& tunnel, + TunnelConfigListenerIfc& listener); + virtual void bind(NamedPipe::ConnectionRef conn_ref); + virtual void on_data(); + void send_virtual_ip(struct in_addr& ip); + NamedPipe::ConnectionRef get_ref() {return _opaque;} + void handle_msg(); + +private: + TunnelChannel& _tunnel; + TunnelConfigListenerIfc& _listener; + char _in_msg[TUNNEL_CONFIG_MAX_MSG_LEN]; // <service_type> <ip> <port> <name> <desc>\n + int _in_msg_len; + + std::string _out_msg; // <virtual ip>\n + int _out_msg_pos; +}; +#endif + +#endif diff --git a/client/windows/platform_utils.cpp b/client/windows/platform_utils.cpp index 6b9049a0..9b809c87 100644 --- a/client/windows/platform_utils.cpp +++ b/client/windows/platform_utils.cpp @@ -90,7 +90,7 @@ HBITMAP get_alpha_bitmap_res(int id) AutoDC auto_dc(create_compatible_dc()); BITMAPINFO dest_info; - uint8_t *dest; + uint8_t* dest; dest_info.bmiHeader.biSize = sizeof(dest_info.bmiHeader); dest_info.bmiHeader.biWidth = src_info.bmWidth; dest_info.bmiHeader.biHeight = -src_info.bmHeight; @@ -102,7 +102,7 @@ HBITMAP get_alpha_bitmap_res(int id) dest_info.bmiHeader.biClrUsed = 0; dest_info.bmiHeader.biClrImportant = 0; - HBITMAP ret = CreateDIBSection(auto_dc.get(), &dest_info, 0, (VOID **)&dest, NULL, 0); + HBITMAP ret = CreateDIBSection(auto_dc.get(), &dest_info, 0, (VOID**)&dest, NULL, 0); if (!ret) { THROW("create bitmap failed, %u", GetLastError()); } @@ -139,7 +139,7 @@ const char* sys_err_to_str(int error) msg = new char[BUF_SIZE]; _snprintf(msg, BUF_SIZE, "errno %d", error); } else { - char *new_line; + char* new_line; if ((new_line = strrchr(msg, '\r'))) { *new_line = 0; } @@ -149,3 +149,14 @@ const char* sys_err_to_str(int error) return errors_map[error]; } +int inet_aton(const char* ip, struct in_addr* in_addr) +{ + unsigned long addr = inet_addr(ip); + + if (addr == INADDR_NONE) { + return 0; + } + in_addr->S_un.S_addr = addr; + return 1; +} + diff --git a/client/windows/platform_utils.h b/client/windows/platform_utils.h index 95b04fcb..299f70b1 100644 --- a/client/windows/platform_utils.h +++ b/client/windows/platform_utils.h @@ -18,13 +18,17 @@ #ifndef _H_PLATFORM_UTILS #define _H_PLATFORM_UTILS +#include <winsock.h> +#include <winsock2.h> +#include <ws2tcpip.h> + #define mb() __asm {lock add [esp], 0} template<class T, class FreeRes = FreeObject<T>, T invalid = NULL > class AutoRes { public: - AutoRes() : res (invalid) {} - AutoRes(T inRes) : res (inRes) {} + AutoRes() : res(invalid) {} + AutoRes(T inRes) : res(inRes) {} ~AutoRes() { set(invalid); } void set(T inRes) {if (res != invalid) free_res(res); res = inRes; } @@ -67,7 +71,7 @@ HBITMAP get_alpha_bitmap_res(int id); class WindowDC { public: - WindowDC(HWND window) : _window (window), _dc (GetDC(window)) {} + WindowDC(HWND window): _window (window), _dc (GetDC(window)) {} ~WindowDC() { ReleaseDC(_window, _dc);} HDC operator * () { return _dc;} @@ -80,5 +84,17 @@ typedef AutoRes<HDC, Delete_DC> AutoReleaseDC; const char* sys_err_to_str(int error); +#define SHUT_WR SD_SEND +#define SHUT_RD SD_RECEIVE +#define SHUT_RDWR SD_BOTH +#define MSG_NOSIGNAL 0 + +#define SHUTDOWN_ERR WSAESHUTDOWN +#define INTERRUPTED_ERR WSAEINTR +#define WOULDBLOCK_ERR WSAEWOULDBLOCK +#define sock_error() WSAGetLastError() +#define sock_err_message(err) sys_err_to_str(err) +int inet_aton(const char* ip, struct in_addr* in_addr); + #endif diff --git a/client/windows/redc.vcproj b/client/windows/redc.vcproj index 25ff3259..90d235ca 100644 --- a/client/windows/redc.vcproj +++ b/client/windows/redc.vcproj @@ -205,6 +205,10 @@ >
</File>
<File
+ RelativePath="..\client_net_socket.cpp"
+ >
+ </File>
+ <File
RelativePath="..\cmd_line_parser.cpp"
>
</File>
@@ -379,6 +383,10 @@ >
</File>
<File
+ RelativePath="..\tunnel_channel.cpp"
+ >
+ </File>
+ <File
RelativePath="..\utils.cpp"
>
</File>
@@ -405,6 +413,10 @@ >
</File>
<File
+ RelativePath="..\client_net_socket.h"
+ >
+ </File>
+ <File
RelativePath="..\common.h"
>
</File>
@@ -557,6 +569,10 @@ >
</File>
<File
+ RelativePath="..\tunnel_channel.h"
+ >
+ </File>
+ <File
RelativePath="..\utils.h"
>
</File>
diff --git a/client/x11/Makefile.am b/client/x11/Makefile.am index 2bb1c853..02ee8717 100644 --- a/client/x11/Makefile.am +++ b/client/x11/Makefile.am @@ -39,6 +39,8 @@ RED_COMMON_SRCS = \ $(top_srcdir)/client/red_cairo_canvas.h \ $(top_srcdir)/client/cmd_line_parser.cpp \ $(top_srcdir)/client/cmd_line_parser.h \ + $(top_srcdir)/client/client_net_socket.cpp \ + $(top_srcdir)/client/client_net_socket.h \ $(top_srcdir)/client/common.h \ $(top_srcdir)/client/cursor_channel.cpp \ $(top_srcdir)/client/cursor_channel.h \ @@ -94,9 +96,11 @@ RED_COMMON_SRCS = \ $(top_srcdir)/client/hot_keys.cpp \ $(top_srcdir)/client/hot_keys.h \ $(top_srcdir)/client/threads.cpp \ + $(top_srcdir)/client/tunnel_channel.cpp \ + $(top_srcdir)/client/tunnel_channel.h \ $(top_srcdir)/client/utils.cpp \ $(top_srcdir)/client/utils.h \ - $(top_srcdir)/client/icon.h \ + $(top_srcdir)/client/icon.h \ $(NULL) bin_PROGRAMS = spicec diff --git a/client/x11/platform_utils.h b/client/x11/platform_utils.h index 763a70e5..1e86f72e 100644 --- a/client/x11/platform_utils.h +++ b/client/x11/platform_utils.h @@ -18,11 +18,28 @@ #ifndef _H_PLATFORM_UTILS #define _H_PLATFORM_UTILS +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netinet/tcp.h> +#include <netdb.h> + #ifdef __i386__ -#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)": : : "memory") +#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%esp)" : : : "memory") #else -#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%rsp)": : : "memory") +#define mb() __asm__ __volatile__ ("lock; addl $0,0(%%rsp)" : : : "memory") #endif +typedef int SOCKET; + +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +#define closesocket(sock) ::close(sock) +#define SHUTDOWN_ERR EPIPE +#define INTERRUPTED_ERR EINTR +#define WOULDBLOCK_ERR EAGAIN +#define sock_error() errno +#define sock_err_message(err) strerror(err) + #endif |