blob: f52874dd8c461177d0aecca21a80b7addf1e67d0 [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 "device_descriptors.h"
#include "op_commands.h"
#include "smart_buffer.h"
#include "usb_printer.h"
#include "usbip.h"
#include "usbip_constants.h"
#include <algorithm>
#include <cerrno>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <base/files/scoped_file.h>
#include <base/logging.h>
// Opens a socket with the option SO_REUSEADDR and returns the file descriptor
// of the socket.
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);
}
// Creates the sockaddr_in |server| and binds the socket |fd| to it.
sockaddr_in BindServerSocket(int fd) {
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);
if (bind(fd, (sockaddr*)&server, sizeof(server)) < 0) {
LOG(ERROR) << "Bind error: " << strerror(errno);
exit(1);
}
return server;
}
// Attemps to accept connections on the network socket |fd|.
base::ScopedFD AcceptConnection(int fd) {
sockaddr_in client;
socklen_t client_length = sizeof(client);
int connection =
accept(fd, 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);
}
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);
}
}
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);
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;
}
void HandleDeviceList(int sockfd, const UsbPrinter& printer) {
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(sockfd, packed_devlist);
}
void ReadBusId(int sockfd) {
char busid[32];
LOG(INFO) << "Attaching device...";
ssize_t received = recv(sockfd, busid, 32, 0);
if (received != 32) {
LOG(ERROR) << "Receive error: " << strerror(errno);
exit(1);
}
}
void HandleAttach(int sockfd, const UsbPrinter& printer) {
OpRepImport rep;
CreateOpRepImport(printer.device_descriptor(),
printer.configuration_descriptor(), &rep);
SmartBuffer packed_import = PackOpRepImport(rep);
SendBuffer(sockfd, packed_import);
}
bool HandleOpRequest(const UsbPrinter& printer, int connection,
bool* attached) {
// 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, &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, printer);
return false;
} else if (request.command == OP_REQ_IMPORT_CMD) {
ReadBusId(connection);
HandleAttach(connection, printer);
*attached = true;
return true;
} else {
LOG(ERROR) << "Unknown command: " << request.command;
return false;
}
}
bool HandleUsbRequest(const UsbPrinter& printer, int connection) {
UsbipCmdSubmit command;
ssize_t received = recv(connection, &command, sizeof(command), 0);
if (received != sizeof(command)) {
if (received == 0) {
LOG(INFO) << "Client closed connection";
return false;
}
LOG(ERROR) << "Receive error: " << strerror(errno);
exit(1);
}
UnpackUsbip(reinterpret_cast<int*>(&command), sizeof(command));
if (command.header.command == COMMAND_USBIP_CMD_SUBMIT) {
printer.HandleUsbRequest(connection, 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 HandleConnection(const UsbPrinter& printer, base::ScopedFD connection) {
bool connection_open = true;
bool attached = false;
while (connection_open) {
if (!attached) {
connection_open = HandleOpRequest(printer, connection.get(), &attached);
} else {
connection_open = HandleUsbRequest(printer, connection.get());
}
}
}
void RunServer(const UsbPrinter& printer) {
base::ScopedFD fd = SetupServerSocket();
sockaddr_in server = BindServerSocket(fd.get());
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.
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.get());
HandleConnection(printer, std::move(connection));
}
}