roughtime.googlesource.com/roughtime.git@v0.0.0-20201210012726-dd529367052d/simple_client.cc (about)

     1  /* Copyright 2016 The Roughtime Authors.
     2   *
     3   * Licensed under the Apache License, Version 2.0 (the "License");
     4   * you may not use this file except in compliance with the License.
     5   * You may obtain a copy of the License at
     6   *
     7   *   http://www.apache.org/licenses/LICENSE-2.0
     8   *
     9   * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License. */
    14  
    15  // simple_client is the most basic Roughtime client possible. Given a filename
    16  // containing a servers list as the sole argument it prints the time obtained
    17  // from a single server and the offset from the current system clock.
    18  
    19  #include <arpa/inet.h>
    20  #include <assert.h>
    21  #include <errno.h>
    22  #include <inttypes.h>
    23  #include <netdb.h>
    24  #include <netinet/udp.h>
    25  #include <sys/socket.h>
    26  #include <sys/types.h>
    27  #include <unistd.h>
    28  
    29  #include <string>
    30  
    31  #include <google/protobuf/stubs/status.h>
    32  #include <google/protobuf/util/json_util.h>
    33  #include <openssl/rand.h>
    34  
    35  #include "client.h"
    36  #include "config.pb.h"
    37  #include "protocol.h"
    38  
    39  // kTimeoutSeconds is the number of seconds that we will wait for a reply
    40  // from the server.
    41  static const int kTimeoutSeconds = 3;
    42  
    43  namespace roughtime {
    44  
    45  // MonotonicUs returns the value of the monotonic clock in microseconds.
    46  uint64_t MonotonicUs();
    47  
    48  // MonotonicUs returns the value of the realtime clock in microseconds.
    49  uint64_t RealtimeUs();
    50  
    51  // GetUsableServer parses the JSON-encoded server information from
    52  // |servers_contents| and looks for the first server with an Ed25519 public key
    53  // and UDP address. If it finds one, it sets |*out_name|, |*out_address| and
    54  // |*out_public_key| and returns true. Otherwise it returns false.
    55  static bool GetUsableServer(std::string* out_name, std::string* out_address,
    56                              std::string* out_public_key,
    57                              const std::string& servers_contents) {
    58    config::ServersJSON servers;
    59    google::protobuf::util::Status status =
    60        google::protobuf::util::JsonStringToMessage(servers_contents, &servers);
    61    if (!status.ok()) {
    62      std::string error_message(status.error_message().data(),
    63                                status.error_message().size());
    64      fprintf(stderr, "Failed to parse servers JSON: %s\n",
    65              error_message.c_str());
    66      return false;
    67    }
    68  
    69    for (int i = 0; i < servers.servers_size(); i++) {
    70      const config::Server& server = servers.servers(i);
    71  
    72      if (server.public_key_type() != "ed25519") {
    73        continue;
    74      }
    75  
    76      for (int j = 0; j < server.addresses_size(); j++) {
    77        const config::ServerAddress& address = server.addresses(j);
    78  
    79        if (address.protocol() != "udp") {
    80          continue;
    81        }
    82  
    83        *out_name = server.name();
    84        *out_address = address.address();
    85        *out_public_key = server.public_key();
    86        return true;
    87      }
    88    }
    89  
    90    fprintf(stderr, "Failed to find any usable servers.\n");
    91    return false;
    92  }
    93  
    94  }  // namespace roughtime
    95  
    96  // ReadServersFile reads the contents of |filename| and sets |*out_contents| to
    97  // contain them. It returns true on success and false on error.
    98  static bool ReadServersFile(std::string* out_contents, const char* filename) {
    99    FILE* servers_file = fopen(filename, "r");
   100    if (servers_file == nullptr) {
   101      fprintf(stderr, "Failed to open JSON servers file.\n");
   102      return false;
   103    }
   104  
   105    if (fseek(servers_file, 0, SEEK_END) != 0) {
   106      fprintf(stderr, "Failed to seek within JSON servers file.\n");
   107      fclose(servers_file);
   108      return false;
   109    }
   110  
   111    const long length = ftell(servers_file);  // NOLINT
   112    if (length < 0) {
   113      fprintf(stderr, "Failed to get offset within JSON servers file.\n");
   114      fclose(servers_file);
   115      return false;
   116    }
   117  
   118    if (fseek(servers_file, 0, SEEK_SET) != 0) {
   119      fprintf(stderr, "Failed to seek within JSON servers file.\n");
   120      fclose(servers_file);
   121      return false;
   122    }
   123  
   124    std::unique_ptr<uint8_t[]> buf(new uint8_t[length]);
   125    if (fread(buf.get(), static_cast<size_t>(length), 1, servers_file) != 1) {
   126      fprintf(stderr, "Failed to read JSON servers file.\n");
   127      fclose(servers_file);
   128      return false;
   129    }
   130  
   131    fclose(servers_file);
   132    out_contents->assign(reinterpret_cast<const char*>(buf.get()), length);
   133    return true;
   134  }
   135  
   136  // CreateSocket resolves the given address (which must be of the form
   137  // "host:port") and sets |*out_socket| to reference a fresh socket connected to
   138  // that address. It returns true on success and false on error.
   139  static bool CreateSocket(int* out_socket, const std::string& address) {
   140    const size_t colon_offset = address.rfind(':');
   141    if (colon_offset == std::string::npos) {
   142      fprintf(stderr, "No port number in server address: %s\n", address.c_str());
   143      return false;
   144    }
   145    std::string host(address.substr(0, colon_offset));
   146    const std::string port_str(address.substr(colon_offset + 1));
   147  
   148    struct addrinfo hints;
   149    memset(&hints, 0, sizeof(hints));
   150    hints.ai_socktype = SOCK_DGRAM;
   151    hints.ai_protocol = IPPROTO_UDP;
   152    hints.ai_flags = AI_NUMERICSERV;
   153  
   154    if (!host.empty() && host[0] == '[' && host[host.size() - 1] == ']') {
   155      host = host.substr(1, host.size() - 1);
   156      hints.ai_family = AF_INET6;
   157      hints.ai_flags |= AI_NUMERICHOST;
   158    }
   159  
   160    struct addrinfo* addrs;
   161    int r = getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrs);
   162    if (r != 0) {
   163      fprintf(stderr, "Failed to resolve %s: %s", address.c_str(),
   164              gai_strerror(r));
   165      return false;
   166    }
   167  
   168    int sock = socket(addrs->ai_family, addrs->ai_socktype, addrs->ai_protocol);
   169    if (sock < 0) {
   170      perror("Failed to create UDP socket");
   171      freeaddrinfo(addrs);
   172      return false;
   173    }
   174  
   175    if (connect(sock, addrs->ai_addr, addrs->ai_addrlen)) {
   176      perror("Failed to connect UDP socket");
   177      freeaddrinfo(addrs);
   178      close(sock);
   179      return false;
   180    }
   181  
   182    char dest_str[INET6_ADDRSTRLEN];
   183    r = getnameinfo(addrs->ai_addr, addrs->ai_addrlen, dest_str, sizeof(dest_str),
   184                    NULL /* don't want port information */, 0, NI_NUMERICHOST);
   185    freeaddrinfo(addrs);
   186  
   187    if (r != 0) {
   188      fprintf(stderr, "getnameinfo: %s", gai_strerror(r));
   189      close(sock);
   190      return false;
   191    }
   192  
   193    printf("Sending request to %s, port %s.\n", dest_str, port_str.c_str());
   194    *out_socket = sock;
   195    return true;
   196  }
   197  
   198  enum ExitCode {
   199    kExitBadSystemTime = 1,
   200    kExitBadArguments = 2,
   201    kExitNoServer = 3,
   202    kExitNetworkError = 4,
   203    kExitTimeout = 5,
   204    kExitBadReply = 6,
   205  };
   206  
   207  int main(int argc, char** argv) {
   208    if (argc != 2) {
   209      fprintf(stderr, "Usage: %s <roughtime-servers.json>\n", argv[0]);
   210      return kExitBadArguments;
   211    }
   212  
   213    std::string servers_contents;
   214    if (!ReadServersFile(&servers_contents, argv[1])) {
   215      return kExitBadArguments;
   216    }
   217  
   218    std::string name, address, public_key;
   219    if (!roughtime::GetUsableServer(&name, &address, &public_key,
   220                                    servers_contents)) {
   221      return kExitNoServer;
   222    }
   223  
   224    int fd = 0;
   225    if (!CreateSocket(&fd, address)) {
   226      return kExitNetworkError;
   227    }
   228  
   229    uint8_t nonce[roughtime::kNonceLength];
   230    RAND_bytes(nonce, sizeof(nonce));
   231    const std::string request = roughtime::CreateRequest(nonce);
   232  
   233    struct timeval timeout;
   234    timeout.tv_sec = kTimeoutSeconds;
   235    timeout.tv_usec = 0;
   236    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
   237  
   238    ssize_t r;
   239    do {
   240      r = send(fd, request.data(), request.size(), 0 /* flags */);
   241    } while (r == -1 && errno == EINTR);
   242    const uint64_t start_us = roughtime::MonotonicUs();
   243  
   244    if (r < 0 || static_cast<size_t>(r) != request.size()) {
   245      perror("send on UDP socket");
   246      close(fd);
   247      return kExitNetworkError;
   248    }
   249  
   250    uint8_t recv_buf[roughtime::kMinRequestSize];
   251    ssize_t buf_len;
   252    do {
   253      buf_len = recv(fd, recv_buf, sizeof(recv_buf), 0 /* flags */);
   254    } while (buf_len == -1 && errno == EINTR);
   255  
   256    const uint64_t end_us = roughtime::MonotonicUs();
   257    const uint64_t end_realtime_us = roughtime::RealtimeUs();
   258  
   259    close(fd);
   260  
   261    if (buf_len == -1) {
   262      if (errno == EINTR) {
   263        fprintf(stderr, "No response from %s with %d seconds.\n", name.c_str(),
   264                kTimeoutSeconds);
   265        return kExitTimeout;
   266      }
   267  
   268      perror("recv from UDP socket");
   269      return kExitNetworkError;
   270    }
   271  
   272    roughtime::rough_time_t timestamp;
   273    uint32_t radius;
   274    std::string error;
   275    if (!roughtime::ParseResponse(
   276            &timestamp, &radius, &error,
   277            reinterpret_cast<const uint8_t*>(public_key.data()), recv_buf,
   278            buf_len, nonce)) {
   279      fprintf(stderr, "Response from %s failed verification: %s", name.c_str(),
   280              error.c_str());
   281      return kExitBadReply;
   282    }
   283  
   284    // We assume that the path to the Roughtime server is symmetric and thus add
   285    // half the round-trip time to the server's timestamp to produce our estimate
   286    // of the current time.
   287    timestamp += (end_us - start_us) / 2;
   288  
   289    printf("Received reply in %" PRIu64 "μs.\n", end_us - start_us);
   290    printf("Current time is %" PRIu64 "μs from the epoch, ±%uμs \n", timestamp,
   291           static_cast<unsigned>(radius));
   292    int64_t system_offset =
   293        static_cast<int64_t>(timestamp) - static_cast<int64_t>(end_realtime_us);
   294    printf("System clock differs from that estimate by %" PRId64 "μs.\n",
   295           system_offset);
   296  
   297    static const int64_t kTenMinutes = 10 * 60 * 1000000;
   298    if (imaxabs(system_offset) > kTenMinutes) {
   299      return kExitBadSystemTime;
   300    }
   301  
   302    return 0;
   303  }