/* Copyright (C) 2009 Red Hat, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . Author: yhalperi@redhat.com */ #include "common.h" #include "tunnel_channel.h" #include #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::CompoundInMessage& full_msg); const uint8_t* data(); uint32_t size(); ClientNetSocket::SendBuffer* ref(); void unref(); protected: virtual ~InSocketMessage() {} private: int _refs; RedChannel::CompoundInMessage& _full_msg; SpiceMsgTunnelSocketData* _sckt_msg; uint32_t _buf_size; }; InSocketMessage::InSocketMessage(RedChannel::CompoundInMessage& full_msg) : _refs (1) , _full_msg (full_msg) { ASSERT(full_msg.type() == SPICE_MSG_TUNNEL_SOCKET_DATA); _full_msg.ref(); _sckt_msg = (SpiceMsgTunnelSocketData*)(_full_msg.data()); _buf_size = _full_msg.size() - sizeof(SpiceMsgTunnelSocketData); } 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() { return _the_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(uint16_t id, SpiceMessageMarshallers *marshallers); static void clear_free_messages(); protected: OutSocketMessage(); virtual ~OutSocketMessage() {} private: static std::list _free_messages; static uint32_t _max_data_size; uint8_t *_the_buf; }; std::list OutSocketMessage::_free_messages; uint32_t OutSocketMessage::_max_data_size; OutSocketMessage::OutSocketMessage() : RedPeer::OutMessage(SPICE_MSGC_TUNNEL_SOCKET_DATA) , RedChannel::OutMessage() , ClientNetSocket::ReceiveBuffer() { } void OutSocketMessage::set_buf_size(uint32_t size) { spice_marshaller_unreserve_space(_marshaller, _max_data_size - size); } 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(uint16_t id, SpiceMessageMarshallers *marshallers) { OutSocketMessage* ret; if (!_free_messages.empty()) { ret = _free_messages.front(); _free_messages.pop_front(); spice_marshaller_reset(ret->marshaller()); } else { ret = new OutSocketMessage(); } SpiceMsgcTunnelSocketData data; data.connection_id = id; marshallers->msgc_tunnel_socket_data(ret->marshaller(), &data); ret->_the_buf = spice_marshaller_reserve_space(ret->marshaller(), _max_data_size); 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, ProcessLoop& process_loop, EventHandler & event_handler, SpiceMessageMarshallers *marshallers); 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(id(), _marshallers);} private: uint32_t _num_tokens; uint32_t _server_num_tokens; uint32_t _service_id; bool _guest_closed; SpiceMessageMarshallers *_marshallers; }; TunnelChannel::TunnelSocket::TunnelSocket(uint16_t id, TunnelService& dst_service, ProcessLoop& process_loop, ClientNetSocket::EventHandler& event_handler, SpiceMessageMarshallers *marshallers) : ClientNetSocket(id, dst_service.ip, htons((uint16_t)dst_service.port), process_loop, event_handler) , _num_tokens (0) , _server_num_tokens (0) , _service_id (dst_service.id) , _guest_closed (false) , _marshallers(marshallers) { } class TunnelHandler: public MessageHandlerImp { public: TunnelHandler(TunnelChannel& channel) : MessageHandlerImp(channel) {} }; TunnelChannel::TunnelChannel(RedClient& client, uint32_t id) : RedChannel(client, SPICE_CHANNEL_TUNNEL, id, new TunnelHandler(*this)) , _max_socket_data_size(0) , _service_id(0) , _service_group(0) #ifdef TUNNEL_CONFIG , _config_listener (NULL) #endif { TunnelHandler* handler = static_cast(get_message_handler()); handler->set_handler(SPICE_MSG_MIGRATE, &TunnelChannel::handle_migrate); handler->set_handler(SPICE_MSG_SET_ACK, &TunnelChannel::handle_set_ack); handler->set_handler(SPICE_MSG_PING, &TunnelChannel::handle_ping); handler->set_handler(SPICE_MSG_WAIT_FOR_CHANNELS, &TunnelChannel::handle_wait_for_channels); handler->set_handler(SPICE_MSG_TUNNEL_INIT, &TunnelChannel::handle_init); handler->set_handler(SPICE_MSG_TUNNEL_SERVICE_IP_MAP, &TunnelChannel::handle_service_ip_map); handler->set_handler(SPICE_MSG_TUNNEL_SOCKET_OPEN, &TunnelChannel::handle_socket_open); handler->set_handler(SPICE_MSG_TUNNEL_SOCKET_CLOSE, &TunnelChannel::handle_socket_close); handler->set_handler(SPICE_MSG_TUNNEL_SOCKET_FIN, &TunnelChannel::handle_socket_fin); handler->set_handler(SPICE_MSG_TUNNEL_SOCKET_TOKEN, &TunnelChannel::handle_socket_token); handler->set_handler(SPICE_MSG_TUNNEL_SOCKET_CLOSED_ACK, &TunnelChannel::handle_socket_closed_ack); handler->set_handler(SPICE_MSG_TUNNEL_SOCKET_DATA, &TunnelChannel::handle_socket_data); } TunnelChannel::~TunnelChannel() { destroy_sockets(); OutSocketMessage::clear_free_messages(); } void TunnelChannel::handle_init(RedPeer::InMessage* message) { SpiceMsgTunnelInit* init_msg = (SpiceMsgTunnelInit*)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) { if (service.type != SPICE_TUNNEL_SERVICE_TYPE_IPP && service.type == SPICE_TUNNEL_SERVICE_TYPE_GENERIC) { THROW("%s: invalid service type", __FUNCTION__); } Message* service_msg = new Message(SPICE_MSGC_TUNNEL_SERVICE_ADD); SpiceMsgcTunnelAddGenericService add; SpiceMarshaller *name_out, *description_out; add.id = service.id; add.group = service.group; add.type = service.type; add.port = service.port; if (service.type == SPICE_TUNNEL_SERVICE_TYPE_IPP) { add.u.ip.type = SPICE_TUNNEL_IP_TYPE_IPv4; } _marshallers->msgc_tunnel_service_add(service_msg->marshaller(), &add, &name_out, &description_out); spice_marshaller_add(name_out, (uint8_t *)service.name.c_str(), service.name.length() + 1); spice_marshaller_add(description_out, (uint8_t *)service.description.c_str(), service.description.length() + 1); post_message(service_msg); } void TunnelChannel::handle_service_ip_map(RedPeer::InMessage* message) { SpiceMsgTunnelServiceIpMap* service_ip_msg = (SpiceMsgTunnelServiceIpMap*)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 == SPICE_TUNNEL_IP_TYPE_IPv4) { memcpy(&service->virtual_ip.s_addr, service_ip_msg->virtual_ip.data, sizeof(SpiceTunnelIPv4)); } 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) { SpiceMsgTunnelSocketOpen* open_msg = (SpiceMsgTunnelSocketOpen*)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_process_loop(), *this, _marshallers); if (sckt->connect(open_msg->tokens)) { _sockets[open_msg->connection_id] = sckt; out_msg = new Message(SPICE_MSGC_TUNNEL_SOCKET_OPEN_ACK); sckt->set_num_tokens(0); sckt->set_server_num_tokens(SOCKET_WINDOW_SIZE); SpiceMsgcTunnelSocketOpenAck ack; ack.connection_id = open_msg->connection_id; ack.tokens = SOCKET_WINDOW_SIZE; _marshallers->msgc_tunnel_socket_open_ack(out_msg->marshaller(), &ack); } else { out_msg = new Message(SPICE_MSGC_TUNNEL_SOCKET_OPEN_NACK); SpiceMsgcTunnelSocketOpenNack nack; nack.connection_id = open_msg->connection_id; _marshallers->msgc_tunnel_socket_open_nack(out_msg->marshaller(), &nack); delete sckt; } post_message(out_msg); } void TunnelChannel::handle_socket_fin(RedPeer::InMessage* message) { SpiceMsgTunnelSocketFin* fin_msg = (SpiceMsgTunnelSocketFin*)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) { SpiceMsgTunnelSocketClose* close_msg = (SpiceMsgTunnelSocketClose*)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 happened 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) { SpiceMsgTunnelSocketClosedAck* close_ack_msg = (SpiceMsgTunnelSocketClosedAck*)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) { SpiceMsgTunnelSocketData* send_msg = (SpiceMsgTunnelSocketData*)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(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) { SpiceMsgTunnelSocketTokens* token_msg = (SpiceMsgTunnelSocketTokens*)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) { OutSocketMessage* out_msg = static_cast(&buf); post_message(out_msg); } void TunnelChannel::on_socket_fin_recv(ClientNetSocket& sckt) { TunnelChannel::TunnelSocket* tunnel_sckt = static_cast(&sckt); Message* out_msg = new Message(SPICE_MSGC_TUNNEL_SOCKET_FIN); DBG(0, "FIN from client coonection id=%d", tunnel_sckt->id()); SpiceMsgcTunnelSocketFin fin; fin.connection_id = tunnel_sckt->id(); _marshallers->msgc_tunnel_socket_fin(out_msg->marshaller(), &fin); post_message(out_msg); } void TunnelChannel::on_socket_disconnect(ClientNetSocket& sckt) { TunnelChannel::TunnelSocket* tunnel_sckt = static_cast(&sckt); Message* out_msg; // close initiated 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(SPICE_MSGC_TUNNEL_SOCKET_CLOSED_ACK); SpiceMsgcTunnelSocketClosedAck ack; ack.connection_id = tunnel_sckt->id(); _marshallers->msgc_tunnel_socket_closed_ack(out_msg->marshaller(), &ack); _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(SPICE_MSGC_TUNNEL_SOCKET_CLOSED); SpiceMsgcTunnelSocketClosed closed; closed.connection_id = tunnel_sckt->id(); _marshallers->msgc_tunnel_socket_closed(out_msg->marshaller(), &closed); } post_message(out_msg); } void TunnelChannel::on_socket_message_send_done(ClientNetSocket& sckt) { TunnelChannel::TunnelSocket* tunnel_sckt = static_cast(&sckt); uint32_t num_tokens = tunnel_sckt->get_num_tokens(); num_tokens++; if (num_tokens == SOCKET_TOKENS_TO_SEND) { Message* out_msg = new Message(SPICE_MSGC_TUNNEL_SOCKET_TOKEN); SpiceMsgcTunnelSocketTokens tokens_msg; tokens_msg.connection_id = tunnel_sckt->id(); tokens_msg.num_tokens = num_tokens; _marshallers->msgc_tunnel_socket_token(out_msg->marshaller(), &tokens_msg); 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::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::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::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; } } } #ifdef TUNNEL_CONFIG void TunnelChannel::on_connect() { _config_listener = new TunnelConfigListenerIfc(*this); } #endif void TunnelChannel::on_disconnect() { destroy_sockets(); OutSocketMessage::clear_free_messages(); #ifdef TUNNEL_CONFIG if (_config_listener) { delete _config_listener; _config_listener = NULL; } #endif } #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(SPICE_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 class CreatePipeListenerEvent: public SyncEvent { public: CreatePipeListenerEvent(NamedPipe::ListenerInterface& listener_ifc) : _listener_ifc (listener_ifc) { } virtual void do_response(AbstractProcessLoop& events_loop) { _listener_ref = NamedPipe::create(TUNNEL_CONFIG_PIPE_NAME, _listener_ifc); } NamedPipe::ListenerRef get_listener() { return _listener_ref;} private: NamedPipe::ListenerInterface& _listener_ifc; NamedPipe::ListenerRef _listener_ref; }; class DestroyPipeListenerEvent: public SyncEvent { public: DestroyPipeListenerEvent(NamedPipe::ListenerRef listener_ref) : _listener_ref (listener_ref) { } virtual void do_response(AbstractProcessLoop& events_loop) { NamedPipe::destroy(_listener_ref); } private: NamedPipe::ListenerRef _listener_ref; }; class DestroyPipeConnectionEvent: public SyncEvent { public: DestroyPipeConnectionEvent(NamedPipe::ConnectionRef ref) : _conn_ref(ref) {} virtual void do_response(AbstractProcessLoop& events_loop) { NamedPipe::destroy_connection(_conn_ref); } private: NamedPipe::ConnectionRef _conn_ref; }; TunnelConfigListenerIfc::TunnelConfigListenerIfc(TunnelChannel& tunnel) : _tunnel (tunnel) { AutoRef event(new CreatePipeListenerEvent(*this)); _tunnel.get_client().push_event(*event); (*event)->wait(); _listener_ref = (*event)->get_listener(); } TunnelConfigListenerIfc::~TunnelConfigListenerIfc() { AutoRef listen_event(new DestroyPipeListenerEvent(_listener_ref)); _tunnel.get_client().push_event(*listen_event); (*listen_event)->wait(); for (std::list::iterator it = _connections.begin(); it != _connections.end(); ++it) { if ((*it)->get_ref() != NamedPipe::INVALID_CONNECTION) { AutoRef conn_event(new DestroyPipeConnectionEvent( (*it)->get_ref())); _tunnel.get_client().push_event(*conn_event); (*conn_event)->wait(); } delete (*it); } } 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 = SPICE_TUNNEL_SERVICE_TYPE_GENERIC; } else if (msg[start_token] == '1') { service_type = SPICE_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