1 //
2 // Copyright (C) 2022 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #include <android-base/logging.h>
17 #include <android-base/strings.h>
18 #include <curl/curl.h>
19 #include <gflags/gflags.h>
20 #include <net/if.h>
21 #include <netinet/in.h>
22 #include <string.h>
23 #include <sys/ioctl.h>
24 #include <sys/time.h>
25 #include <sys/utsname.h>
26 #include <chrono>
27 #include <ctime>
28 #include <iostream>
29 
30 #include "common/libs/utils/tee_logging.h"
31 #include "host/commands/metrics/metrics_defs.h"
32 #include "host/commands/metrics/utils.h"
33 
34 using cuttlefish::MetricsExitCodes;
35 
36 namespace metrics {
37 
Hashing(const std::string & input)38 static std::string Hashing(const std::string& input) {
39   const std::hash<std::string> hasher;
40   return std::to_string(hasher(input));
41 }
42 
GetOsName()43 std::string GetOsName() {
44   struct utsname buf;
45   if (uname(&buf) != 0) {
46     LOG(ERROR) << "failed to retrieve system information";
47     return "Error";
48   }
49   return std::string(buf.sysname);
50 }
51 
GenerateSessionId(uint64_t now_ms)52 std::string GenerateSessionId(uint64_t now_ms) {
53   uint64_t now_day = now_ms / 1000 / 60 / 60 / 24;
54   return Hashing(GetMacAddress() + std::to_string(now_day));
55 }
56 
GetCfVersion()57 std::string GetCfVersion() {
58   // TODO: per ellisr@ leave empty for now
59   return "";
60 }
61 
GetOsVersion()62 std::string GetOsVersion() {
63   struct utsname buf;
64   if (uname(&buf) != 0) {
65     LOG(ERROR) << "failed to retrieve system information";
66   }
67   std::string version = buf.release;
68   return version;
69 }
70 
GetMacAddress()71 std::string GetMacAddress() {
72   int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
73   if (sock == -1) {
74     LOG(ERROR) << "couldn't connect to socket";
75     return "";
76   }
77 
78   char buf2[1024];
79   struct ifconf ifc;
80   ifc.ifc_len = sizeof(buf2);
81   ifc.ifc_buf = buf2;
82   if (ioctl(sock, SIOCGIFCONF, &ifc) == -1) {
83     LOG(ERROR) << "couldn't connect to socket";
84     return "";
85   }
86 
87   struct ifreq* it = ifc.ifc_req;
88   const struct ifreq* const end = it + (ifc.ifc_len / sizeof(struct ifreq));
89 
90   unsigned char mac_address[6] = {0};
91   struct ifreq ifr;
92   for (; it != end; ++it) {
93     strcpy(ifr.ifr_name, it->ifr_name);
94     if (ioctl(sock, SIOCGIFFLAGS, &ifr) != 0) {
95       LOG(ERROR) << "couldn't connect to socket";
96       return "";
97     }
98     if (ifr.ifr_flags & IFF_LOOPBACK) {
99       continue;
100     }
101     if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) {
102       memcpy(mac_address, ifr.ifr_hwaddr.sa_data, 6);
103       break;
104     }
105   }
106 
107   char mac[100];
108   sprintf(mac, "%02x:%02x:%02x:%02x:%02x:%02x", mac_address[0], mac_address[1],
109           mac_address[2], mac_address[3], mac_address[4], mac_address[5]);
110   return mac;
111 }
112 
GetCompany()113 std::string GetCompany() {
114   // TODO: per ellisr@ leave hard-coded for now
115   return "GOOGLE";
116 }
117 
GetVmmVersion()118 std::string GetVmmVersion() {
119   // TODO: per ellisr@ leave empty for now
120   return "";
121 }
122 
GetEpochTimeMs()123 uint64_t GetEpochTimeMs() {
124   auto now = std::chrono::system_clock::now().time_since_epoch();
125   uint64_t milliseconds_since_epoch =
126       std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
127   return milliseconds_since_epoch;
128 }
129 
curl_out_writer(char * response,size_t size,size_t nmemb,void * userdata)130 size_t curl_out_writer([[maybe_unused]] char* response, size_t size,
131                        size_t nmemb, [[maybe_unused]] void* userdata) {
132   return size * nmemb;
133 }
134 
SetCurlUrlPart(CURLU * url,CURLUPart part,const char * value)135 CURLUcode SetCurlUrlPart(CURLU* url, CURLUPart part, const char* value) {
136   CURLUcode urc = curl_url_set(url, part, value, 0);
137   if (urc != 0) {
138     LOG(ERROR) << "Failed to set url part '" << part << "' to '" << value
139                << "': Error '" << curl_url_strerror(urc) << "'";
140   }
141   return urc;
142 }
143 
ClearcutServerUrl(metrics::ClearcutServer server)144 std::string ClearcutServerUrl(metrics::ClearcutServer server) {
145   switch (server) {
146     case metrics::kLocal:
147       return "http://localhost:27910/log";
148 
149     case metrics::kStaging:
150       return "https://play.googleapis.com:443/staging/log";
151 
152     case metrics::kProd:
153       return "https://play.googleapis.com:443/log";
154 
155     default:
156       LOG(FATAL) << "Invalid host configuration";
157       return "";
158   }
159 }
160 
PostRequest(const std::string & output,metrics::ClearcutServer server)161 MetricsExitCodes PostRequest(const std::string& output,
162                              metrics::ClearcutServer server) {
163   std::string clearcut_url = ClearcutServerUrl(server);
164 
165   std::unique_ptr<CURLU, void (*)(CURLU*)> url(curl_url(), curl_url_cleanup);
166   if (!url) {
167     LOG(ERROR) << "Failed to initialize CURLU.";
168     return cuttlefish::kMetricsError;
169   }
170 
171   CURLUcode urc =
172       curl_url_set(url.get(), CURLUPART_URL, clearcut_url.c_str(), 0);
173   if (urc != 0) {
174     LOG(ERROR) << "Failed to set url to " << url.get() << clearcut_url
175                << "': " << curl_url_strerror(urc) << "'";
176     return cuttlefish::kMetricsError;
177   }
178   curl_global_init(CURL_GLOBAL_ALL);
179 
180   std::unique_ptr<CURL, void (*)(CURL*)> curl(curl_easy_init(),
181                                               curl_easy_cleanup);
182 
183   if (!curl) {
184     LOG(ERROR) << "Failed to initialize CURL.";
185     return cuttlefish::kMetricsError;
186   }
187 
188   curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, &curl_out_writer);
189   curl_easy_setopt(curl.get(), CURLOPT_SSL_VERIFYPEER, 0L);
190   curl_easy_setopt(curl.get(), CURLOPT_CURLU, url.get());
191   curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, output.data());
192   curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDSIZE, output.size());
193   CURLcode rc = curl_easy_perform(curl.get());
194   long http_code = 0;
195   curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code);
196 
197   if (rc == CURLE_ABORTED_BY_CALLBACK || http_code != 200) {
198     LOG(ERROR) << "Metrics message failed: [" << output << "]";
199     LOG(ERROR) << "http error code: " << http_code;
200     LOG(ERROR) << "curl error code: " << rc << " | " << curl_easy_strerror(rc);
201     return cuttlefish::kMetricsError;
202   }
203   LOG(INFO) << "Metrics posted to ClearCut";
204   curl_global_cleanup();
205   return cuttlefish::kSuccess;
206 }
207 }  // namespace metrics
208