blob: d734e64361d9863e1e0147cb01cb1c2c5454fa95 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "garnet/bin/network_time/roughtime_server.h"
#include <string>
#include <client.h>
#include <errno.h>
#include <netdb.h>
#include <openssl/rand.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <lib/fit/defer.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include "lib/fxl/files/unique_fd.h"
#include "lib/syslog/cpp/logger.h"
namespace time_server {
bool RoughTimeServer::IsValid() const { return valid_; }
std::pair<Status, std::optional<zx::time_utc>>
RoughTimeServer::GetTimeFromServer() const {
if (!IsValid()) {
FX_LOGS(ERROR) << "time server not supported: " << address_;
return {NOT_SUPPORTED, {}};
}
// Create Socket
const size_t colon_offset = address_.rfind(':');
if (colon_offset == std::string::npos) {
FX_LOGS(ERROR) << "no port number in server address: " << address_;
return {NOT_SUPPORTED, {}};
}
std::string host(address_.substr(0, colon_offset));
const std::string port_str(address_.substr(colon_offset + 1));
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_NUMERICSERV;
if (!host.empty() && host[0] == '[' && host[host.size() - 1] == ']') {
host = host.substr(1, host.size() - 1);
hints.ai_family = AF_INET6;
hints.ai_flags |= AI_NUMERICHOST;
}
struct addrinfo* addrs;
int err = getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrs);
if (err != 0) {
// Log once to cut down on logspam.
// TODO: Support general LOG_EVERY_N to solve this kind of problem.
if (!logged_once_) {
FX_LOGS(ERROR) << "resolving " << address_ << ": " << gai_strerror(err);
logged_once_ = true;
}
return {NETWORK_ERROR, {}};
}
auto ac1 = fit::defer([&]() { freeaddrinfo(addrs); });
fxl::UniqueFD sock_ufd(
socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol));
if (!sock_ufd.is_valid()) {
FX_LOGS(ERROR) << "creating UDP socket: " << strerror(errno);
return {NETWORK_ERROR, {}};
}
int sock_fd = sock_ufd.get();
if (connect(sock_fd, addrs->ai_addr, addrs->ai_addrlen)) {
FX_LOGS(ERROR) << "connecting UDP socket: " << strerror(errno);
return {NETWORK_ERROR, {}};
}
char dest_str[INET6_ADDRSTRLEN];
err = getnameinfo(addrs->ai_addr, addrs->ai_addrlen, dest_str,
sizeof(dest_str), NULL, 0, NI_NUMERICHOST);
if (err != 0) {
FX_LOGS(ERROR) << "getnameinfo: " << gai_strerror(err);
return {NETWORK_ERROR, {}};
}
FX_VLOGS(1) << "Sending request to " << dest_str << ", port " << port_str;
uint8_t nonce[roughtime::kNonceLength];
RAND_bytes(nonce, sizeof(nonce));
const std::string request = roughtime::CreateRequest(nonce);
int timeout = 3 * 1000; // in milliseconds
ssize_t r;
do {
r = send(sock_fd, request.data(), request.size(), 0);
} while (r == -1 && errno == EINTR);
// clock_get returns ns since start of clock. See
// zircon/docs/syscalls/clock_get.md.
const zx::time start{zx_clock_get(ZX_CLOCK_MONOTONIC)};
if (r < 0 || static_cast<size_t>(r) != request.size()) {
FX_LOGS(ERROR) << "send on UDP socket" << strerror(errno);
return {NETWORK_ERROR, {}};
}
uint8_t recv_buf[roughtime::kMinRequestSize];
ssize_t buf_len;
pollfd readfd;
readfd.fd = sock_fd;
readfd.events = POLLIN;
fd_set readfds;
FD_SET(sock_fd, &readfds);
int ret = poll(&readfd, 1, timeout);
if (ret < 0) {
FX_LOGS(ERROR) << "poll on UDP socket: " << strerror(errno);
return {NETWORK_ERROR, {}};
}
if (ret == 0) {
FX_LOGS(ERROR) << "timeout while poll";
return {NETWORK_ERROR, {}};
}
if (readfd.revents != POLLIN) {
FX_LOGS(ERROR) << "poll, revents = " << readfd.revents;
return {NETWORK_ERROR, {}};
}
buf_len = recv(sock_fd, recv_buf, sizeof(recv_buf), 0 /* flags */);
const zx::time end{zx_clock_get(ZX_CLOCK_MONOTONIC)};
const zx::duration drift = (end - start) / 2;
if (buf_len == -1) {
FX_LOGS(ERROR) << "recv from UDP socket: " << strerror(errno);
return {NETWORK_ERROR, {}};
}
uint32_t radius;
std::string error;
uint64_t timestamp_us;
if (!roughtime::ParseResponse(&timestamp_us, &radius, &error, public_key_,
recv_buf, buf_len, nonce)) {
FX_LOGS(ERROR) << "response from " << address_
<< " failed verification: " << error;
return {BAD_RESPONSE, {}};
}
// zx_time_t is nanoseconds, timestamp_us is microseconds.
zx::time_utc timestamp{ZX_USEC(timestamp_us)};
return {OK, timestamp - drift};
}
} // namespace time_server