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 ×tamp, &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 }