blob: fae1abacd06e2537b8e914150406c510f71a0f9b [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "server.h"
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <algorithm>
#include <cerrno>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <utility>
#include <base/check_op.h>
#include <base/logging.h>
#include "device_descriptors.h"
#include "op_commands.h"
#include "usbip.h"
#include "usbip_constants.h"
namespace {
// Attempts to create the socket used for accepting connections on the server,
// and if successful returns the file descriptor of the socket.
//
// Opens a socket with the option SO_REUSEADDR;
base::ScopedFD SetupServerSocket() {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
LOG(ERROR) << "Socket error: " << strerror(errno);
exit(1);
}
int reuse = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
LOG(ERROR) << "setsockopt(SO_REUSEADDR) failed";
}
return base::ScopedFD(fd);
}
// Binds the server socket described by |fd| to an address and returns the
// resulting sockaddr_in struct which contains the address.
sockaddr_in BindServerSocket(const base::ScopedFD& sockfd) {
sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = htonl(INADDR_ANY);
server.sin_port = htons(TCP_SERV_PORT);
sockaddr* server_socket = reinterpret_cast<sockaddr*>(&server);
if (bind(sockfd.get(), server_socket, sizeof(server)) < 0) {
LOG(ERROR) << "Bind error: " << strerror(errno);
exit(1);
}
return server;
}
// Accepts a new connection to the server described by |fd| and returns the file
// descriptor of the connection.
base::ScopedFD AcceptConnection(const base::ScopedFD& fd) {
sockaddr_in client;
socklen_t client_length = sizeof(client);
int connection =
accept(fd.get(), reinterpret_cast<sockaddr*>(&client), &client_length);
if (connection < 0) {
LOG(ERROR) << "Accept error: " << strerror(errno);
exit(1);
}
LOG(INFO) << "Connection address: " << inet_ntoa(client.sin_addr) << ":"
<< client.sin_port;
return base::ScopedFD(connection);
}
// Converts the bound address in |server| and reports it in a human-readable
// format.
void ReportBoundAddress(sockaddr_in* server) {
char address[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &server->sin_addr, address, INET_ADDRSTRLEN)) {
LOG(INFO) << "Bound server to address " << address << ":"
<< server->sin_port;
} else {
LOG(ERROR) << "inet_ntop: " << strerror(errno);
exit(1);
}
}
// Reads the requested bus ID for an OpReqImport message. Since we are only
// exporting a single device we should only ever expect to receive the value for
// the exported device, so this function is meant to simply clear the data from
// the socket.
void ReadBusId(const base::ScopedFD& sockfd) {
char busid[32];
LOG(INFO) << "Attaching device...";
ssize_t received = recv(sockfd.get(), busid, 32, 0);
if (received != 32) {
LOG(ERROR) << "Receive error: " << strerror(errno);
exit(1);
}
}
} // namespace
void SendBuffer(int sockfd, const SmartBuffer& smart_buffer) {
size_t remaining = smart_buffer.size();
size_t total = 0;
while (remaining > 0) {
size_t to_send = std::min<size_t>(remaining, 512UL);
ssize_t sent =
send(sockfd, smart_buffer.data() + total, to_send, MSG_NOSIGNAL);
CHECK_GE(sent, 0) << "Failed to write data to socket";
CHECK_GE(remaining, sent) << "Sent more data than expected";
size_t sent_unsigned = static_cast<size_t>(sent);
total += sent_unsigned;
remaining -= sent_unsigned;
}
}
SmartBuffer ReceiveBuffer(int sockfd, size_t size) {
SmartBuffer smart_buffer(size);
auto buf = std::make_unique<uint8_t[]>(size);
size_t remaining = size;
size_t total = 0;
while (total < size) {
size_t to_receive = std::min<size_t>(remaining, 512UL);
ssize_t received = recv(sockfd, buf.get() + total, to_receive, 0);
if (received == 0 && size != 0) {
LOG(ERROR) << "Client has closed connection";
return smart_buffer;
}
CHECK_GE(received, 0) << "Failed to receive data from socket";
CHECK_GE(remaining, received) << "Received more data than expected";
size_t received_unsigned = static_cast<size_t>(received);
total += received_unsigned;
remaining -= received_unsigned;
}
smart_buffer.Add(buf.get(), size);
return smart_buffer;
}
Server::Server(UsbPrinter printer) : printer_(std::move(printer)) {}
void Server::Run() {
base::ScopedFD fd = SetupServerSocket();
sockaddr_in server = BindServerSocket(fd);
ReportBoundAddress(&server);
if (listen(fd.get(), SOMAXCONN) < 0) {
LOG(ERROR) << "Listen error: " << strerror(errno);
exit(1);
}
// Print notification that the server is ready to begin accepting connections.
//
// TODO(b/186000173): The tast function `usbprinter.waitLaunch()`
// relies on this message appearing on stdout. This may change when
// we change `virtual-usb-printer` to trigger via Upstart.
printf("virtual-usb-printer: ready to accept connections\n");
while (true) {
// Will block until a new connection has been accepted.
base::ScopedFD connection = AcceptConnection(fd);
HandleConnection(std::move(connection));
}
}
void Server::HandleDeviceList(const base::ScopedFD& connection) const {
OpRepDevlist list;
LOG(INFO) << "Listing devices...";
CreateOpRepDevlist(printer_.device_descriptor(),
printer_.configuration_descriptor(),
printer_.interface_descriptors(), &list);
SmartBuffer packed_devlist = PackOpRepDevlist(list);
free(list.interfaces);
SendBuffer(connection.get(), packed_devlist);
}
void Server::HandleAttach(const base::ScopedFD& connection) const {
OpRepImport rep;
CreateOpRepImport(printer_.device_descriptor(),
printer_.configuration_descriptor(), &rep);
SmartBuffer packed_import = PackOpRepImport(rep);
SendBuffer(connection.get(), packed_import);
}
bool Server::HandleOpRequest(const base::ScopedFD& connection,
bool* attached) const {
// Read in the header first in order to determine whether the request is an
// OpReqDevlist or an OpReqImport.
OpHeader request;
ssize_t received = recv(connection.get(), &request, sizeof(request), 0);
if (received != sizeof(request)) {
LOG(ERROR) << "Receive error: " << strerror(errno);
exit(1);
}
UnpackOpHeader(&request);
if (request.command == OP_REQ_DEVLIST_CMD) {
HandleDeviceList(connection);
return false;
} else if (request.command == OP_REQ_IMPORT_CMD) {
ReadBusId(connection);
HandleAttach(connection);
*attached = true;
return true;
} else {
LOG(ERROR) << "Unknown command: " << request.command;
return false;
}
}
bool Server::HandleUsbRequest(const base::ScopedFD& connection) {
SmartBuffer received =
ReceiveBuffer(connection.get(), sizeof(UsbipCmdSubmit));
UsbipCmdSubmit command = UnpackUsbipCmdSubmit(&received);
if (command.header.command == COMMAND_USBIP_CMD_SUBMIT) {
printer_.HandleUsbRequest(connection.get(), command);
return true;
} else if (command.header.command == COMMAND_USBIP_CMD_UNLINK) {
LOG(INFO) << "Received unlink URB...ignoring";
LOG(INFO) << "Unlinked seqnum : " << command.transfer_flags;
return true;
} else {
LOG(ERROR) << "Unknown USBIP command " << command.header.command;
return false;
}
}
void Server::HandleConnection(base::ScopedFD connection) {
bool connection_open = true;
bool attached = false;
while (connection_open) {
if (!attached) {
connection_open = HandleOpRequest(connection, &attached);
} else {
connection_open = HandleUsbRequest(connection);
}
}
}