blob: a9894c3c06adb16a1d8e50028c5524f3da75c228 [file] [log] [blame]
// Copyright 2015 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 "lorgnette/epson_probe.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <map>
#include <string>
#include <base/files/scoped_file.h>
#include <base/logging.h>
#include <base/posix/eintr_wrapper.h>
#include <base/stl_util.h>
#include <chromeos/dbus/service_constants.h>
#include "lorgnette/firewall_manager.h"
namespace lorgnette {
namespace epson_probe {
namespace {
const uint16_t kEpsonProbePort = 3289;
const char kEpsonDeviceNamePrefix[] = "epson2:net:";
const int kSyscallSuccess = 0;
const char kScannerManufacturerEpson[] = "Epson";
const char kScannerModelNetwork[] = "Network";
const char kScannerTypeFlatbed[] = "flatbed scanner";
const char kProbePacket[] = "EPSONP\0\xff\0\0\0\0\0\0\0";
const char kReplyPrefix[] = "EPSON";
const int kExpectedReplySize = 76;
const int kReplyWaitTimeSeconds = 1;
static_assert(sizeof(kReplyPrefix) < kExpectedReplySize,
"Reply prefix should be smaller than the expected reply size");
} // namespace
static bool GetTimeMonotonic(struct timeval* tv) {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
return false;
}
tv->tv_sec = ts.tv_sec;
tv->tv_usec = ts.tv_nsec / 1000;
return true;
}
static bool CreateBroadcastSocket(int *broadcast_socket, uint16_t *port) {
int sock = HANDLE_EINTR(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
if (sock < 0) {
PLOG(ERROR) << "socket() returns " << sock;
return false;
}
base::ScopedFD scoped_socket(sock);
CHECK(sock < FD_SETSIZE);
int result = HANDLE_EINTR(
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK));
if (result < 0) {
PLOG(ERROR) << "fcntl(NONBLOCK) returns " << result;
return false;
}
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
result = HANDLE_EINTR(
bind(sock, reinterpret_cast<struct sockaddr*>(&local), sizeof(local)));
if (result != kSyscallSuccess) {
PLOG(ERROR) << "bind() returns " << result;
return false;
}
int broadcast_enable = 1;
result = HANDLE_EINTR(
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast_enable,
sizeof(broadcast_enable)));
if (result != kSyscallSuccess) {
PLOG(ERROR) << "setsockopt(SO_BROADCAST) returns " << result;
return false;
}
socklen_t local_len = sizeof(local);
result = HANDLE_EINTR(
getsockname(sock, reinterpret_cast<struct sockaddr*>(&local),
&local_len));
if (result != kSyscallSuccess || local.sin_port == 0) {
PLOG(ERROR) << "getsockname() returns " << result;
return false;
}
*broadcast_socket = scoped_socket.release();
*port = ntohs(local.sin_port);
LOG(INFO) << "Bound to port " << ntohs(*port);
return true;
}
static void SendProbeAndListen(uint16_t probe_socket,
Manager::ScannerInfo* scanner_list) {
struct sockaddr_in broadcast;
memset(&broadcast, 0, sizeof(broadcast));
broadcast.sin_family = AF_INET;
broadcast.sin_addr.s_addr = INADDR_BROADCAST;
broadcast.sin_port = htons(kEpsonProbePort);
int result = HANDLE_EINTR(
sendto(probe_socket, kProbePacket, sizeof(kProbePacket), 0,
reinterpret_cast<struct sockaddr*>(&broadcast),
sizeof(broadcast)));
if (result != sizeof(kProbePacket)) {
LOG(ERROR) << "sendto() returns " << result
<< ": " << (result < 0 ? strerror(errno) : "");
return;
}
struct timeval maximum_wait_duration = { kReplyWaitTimeSeconds, 0 };
struct timeval now, end_time;
CHECK(GetTimeMonotonic(&now));
timeradd(&now, &maximum_wait_duration, &end_time);
do {
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(probe_socket, &read_fds);
struct timeval wait_duration;
timersub(&end_time, &now, &wait_duration);
result = HANDLE_EINTR(
select(probe_socket + 1, &read_fds, nullptr, nullptr, &wait_duration));
if (result < 0) {
PLOG(ERROR) << "select() returns " << result;
break;
}
if (result == 0) {
break;
}
struct sockaddr_in remote;
memset(&remote, 0, sizeof(remote));
socklen_t remote_len = sizeof(remote);
char response[kExpectedReplySize];
result = HANDLE_EINTR(
recvfrom(probe_socket, response, sizeof(response), 0,
reinterpret_cast<struct sockaddr*>(&remote),
&remote_len));
if (result < 0) {
PLOG(ERROR) << "recvfrom() returns " << result;
break;
}
if (result == kExpectedReplySize &&
memcmp(response, kReplyPrefix, sizeof(kReplyPrefix) - 1) == 0) {
char* ip_address_string = inet_ntoa(remote.sin_addr);
std::string device_name =
std::string(kEpsonDeviceNamePrefix) + ip_address_string;
if (!ContainsKey(*scanner_list, device_name)) {
// We don't use anything from the response; neither does sane-backends.
std::map<std::string, std::string> scanner_info;
scanner_info[kScannerPropertyManufacturer] = kScannerManufacturerEpson;
scanner_info[kScannerPropertyModel] = kScannerModelNetwork;
scanner_info[kScannerPropertyType] = kScannerTypeFlatbed;
(*scanner_list)[device_name] = scanner_info;
} else {
LOG(INFO) << "Not adding device " << device_name
<< "; already in list";
}
} else {
LOG(ERROR) << "Unexpected reply; length was " << result;
}
CHECK(GetTimeMonotonic(&now));
} while (timercmp(&now, &end_time, <));
}
void ProbeForScanners(FirewallManager* firewall_manager,
Manager::ScannerInfo* scanner_list) {
int probe_socket;
uint16_t local_port;
if (!CreateBroadcastSocket(&probe_socket, &local_port)) {
return;
}
base::ScopedFD scoped_socket(probe_socket);
firewall_manager->RequestUdpPortAccess(local_port);
SendProbeAndListen(probe_socket, scanner_list);
firewall_manager->ReleaseUdpPortAccess(local_port);
}
} // namespace epson_probe
} // namespace lorgnette