1 /*
2  * Copyright (C) 2020 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 "host/libs/allocd/alloc_utils.h"
17 
18 #include <cstdint>
19 #include <fstream>
20 
21 #include "android-base/logging.h"
22 
23 namespace cuttlefish {
24 
RunExternalCommand(const std::string & command)25 int RunExternalCommand(const std::string& command) {
26   FILE* fp;
27   LOG(INFO) << "Running external command: " << command;
28   fp = popen(command.c_str(), "r");
29 
30   if (fp == nullptr) {
31     LOG(WARNING) << "Error running external command";
32     return -1;
33   }
34 
35   int status = pclose(fp);
36   int ret = -1;
37   if (status == -1) {
38     LOG(WARNING) << "pclose error";
39   } else {
40     if (WIFEXITED(status)) {
41       LOG(INFO) << "child process exited normally";
42       ret = WEXITSTATUS(status);
43     } else if (WIFSIGNALED(status)) {
44       LOG(WARNING) << "child process was terminated by a signal";
45     } else {
46       LOG(WARNING) << "child process did not terminate normally";
47     }
48   }
49   return ret;
50 }
51 
AddTapIface(const std::string & name)52 bool AddTapIface(const std::string& name) {
53   std::stringstream ss;
54   ss << "ip tuntap add dev " << name << " mode tap group cvdnetwork vnet_hdr";
55   auto add_command = ss.str();
56   LOG(INFO) << "Create tap interface: " << add_command;
57   int status = RunExternalCommand(add_command);
58   return status == 0;
59 }
60 
ShutdownIface(const std::string & name)61 bool ShutdownIface(const std::string& name) {
62   std::stringstream ss;
63   ss << "ip link set dev " << name << " down";
64   auto link_command = ss.str();
65   LOG(INFO) << "Shutdown tap interface: " << link_command;
66   int status = RunExternalCommand(link_command);
67 
68   return status == 0;
69 }
70 
BringUpIface(const std::string & name)71 bool BringUpIface(const std::string& name) {
72   std::stringstream ss;
73   ss << "ip link set dev " << name << " up";
74   auto link_command = ss.str();
75   LOG(INFO) << "Bring up tap interface: " << link_command;
76   int status = RunExternalCommand(link_command);
77 
78   return status == 0;
79 }
80 
CreateEthernetIface(const std::string & name,const std::string & bridge_name,bool has_ipv4_bridge,bool has_ipv6_bridge,bool use_ebtables_legacy)81 bool CreateEthernetIface(const std::string& name, const std::string& bridge_name,
82                          bool has_ipv4_bridge, bool has_ipv6_bridge,
83                          bool use_ebtables_legacy) {
84   // assume bridge exists
85 
86   EthernetNetworkConfig config{false, false, false};
87 
88   if (!CreateTap(name)) {
89     return false;
90   }
91 
92   config.has_tap = true;
93 
94   if (!LinkTapToBridge(name, bridge_name)) {
95     CleanupEthernetIface(name, config);
96     return false;
97   }
98 
99   if (!has_ipv4_bridge) {
100     if (!CreateEbtables(name, true, use_ebtables_legacy)) {
101       CleanupEthernetIface(name, config);
102       return false;
103     }
104     config.has_broute_ipv4 = true;
105   }
106 
107   if (!has_ipv6_bridge) {
108     if (CreateEbtables(name, false, use_ebtables_legacy)) {
109       CleanupEthernetIface(name, config);
110       return false;
111     }
112     config.has_broute_ipv6 = true;
113   }
114 
115   return true;
116 }
117 
MobileGatewayName(const std::string & ipaddr,uint16_t id)118 std::string MobileGatewayName(const std::string& ipaddr, uint16_t id) {
119   std::stringstream ss;
120   ss << ipaddr << "." << (4 * id + 1);
121   return ss.str();
122 }
123 
MobileNetworkName(const std::string & ipaddr,const std::string & netmask,uint16_t id)124 std::string MobileNetworkName(const std::string& ipaddr,
125                               const std::string& netmask, uint16_t id) {
126   std::stringstream ss;
127   ss << ipaddr << "." << (4 * id) << netmask;
128   return ss.str();
129 }
130 
CreateMobileIface(const std::string & name,uint16_t id,const std::string & ipaddr)131 bool CreateMobileIface(const std::string& name, uint16_t id,
132                        const std::string& ipaddr) {
133   if (id > kMaxIfaceNameId) {
134     LOG(ERROR) << "ID exceeds maximum value to assign a netmask: " << id;
135     return false;
136   }
137 
138   auto netmask = "/30";
139   auto gateway = MobileGatewayName(ipaddr, id);
140   auto network = MobileNetworkName(ipaddr, netmask, id);
141 
142   if (!CreateTap(name)) {
143     return false;
144   }
145 
146   if (!AddGateway(name, gateway, netmask)) {
147     DestroyIface(name);
148   }
149 
150   if (!IptableConfig(network, true)) {
151     DestroyGateway(name, gateway, netmask);
152     DestroyIface(name);
153     return false;
154   };
155 
156   return true;
157 }
158 
DestroyMobileIface(const std::string & name,uint16_t id,const std::string & ipaddr)159 bool DestroyMobileIface(const std::string& name, uint16_t id,
160                         const std::string& ipaddr) {
161   if (id > 63) {
162     LOG(ERROR) << "ID exceeds maximum value to assign a netmask: " << id;
163     return false;
164   }
165 
166   auto netmask = "/30";
167   auto gateway = MobileGatewayName(ipaddr, id);
168   auto network = MobileNetworkName(ipaddr, netmask, id);
169 
170   IptableConfig(network, false);
171   DestroyGateway(name, gateway, netmask);
172   return DestroyIface(name);
173 }
174 
AddGateway(const std::string & name,const std::string & gateway,const std::string & netmask)175 bool AddGateway(const std::string& name, const std::string& gateway,
176                 const std::string& netmask) {
177   std::stringstream ss;
178   ss << "ip addr add " << gateway << netmask << " broadcast + dev " << name;
179   auto command = ss.str();
180   LOG(INFO) << "setup gateway: " << command;
181   int status = RunExternalCommand(command);
182 
183   return status == 0;
184 }
185 
DestroyGateway(const std::string & name,const std::string & gateway,const std::string & netmask)186 bool DestroyGateway(const std::string& name, const std::string& gateway,
187                     const std::string& netmask) {
188   std::stringstream ss;
189   ss << "ip addr del " << gateway << netmask << " broadcast + dev " << name;
190   auto command = ss.str();
191   LOG(INFO) << "removing gateway: " << command;
192   int status = RunExternalCommand(command);
193 
194   return status == 0;
195 }
196 
DestroyEthernetIface(const std::string & name,bool has_ipv4_bridge,bool has_ipv6_bridge,bool use_ebtables_legacy)197 bool DestroyEthernetIface(const std::string& name, bool has_ipv4_bridge,
198                           bool has_ipv6_bridge, bool use_ebtables_legacy) {
199   if (!has_ipv6_bridge) {
200     DestroyEbtables(name, false, use_ebtables_legacy);
201   }
202 
203   if (!has_ipv4_bridge) {
204     DestroyEbtables(name, true, use_ebtables_legacy);
205   }
206 
207   return DestroyIface(name);
208 }
209 
CleanupEthernetIface(const std::string & name,const EthernetNetworkConfig & config)210 void CleanupEthernetIface(const std::string& name,
211                           const EthernetNetworkConfig& config) {
212   if (config.has_broute_ipv6) {
213     DestroyEbtables(name, false, config.use_ebtables_legacy);
214   }
215 
216   if (config.has_broute_ipv4) {
217     DestroyEbtables(name, true, config.use_ebtables_legacy);
218   }
219 
220   if (config.has_tap) {
221     DestroyIface(name);
222   }
223 }
224 
CreateEbtables(const std::string & name,bool use_ipv4,bool use_ebtables_legacy)225 bool CreateEbtables(const std::string& name, bool use_ipv4,
226                     bool use_ebtables_legacy) {
227   return EbtablesBroute(name, use_ipv4, true, use_ebtables_legacy) &&
228          EbtablesFilter(name, use_ipv4, true, use_ebtables_legacy);
229 }
230 
DestroyEbtables(const std::string & name,bool use_ipv4,bool use_ebtables_legacy)231 bool DestroyEbtables(const std::string& name, bool use_ipv4,
232                      bool use_ebtables_legacy) {
233   return EbtablesBroute(name, use_ipv4, false, use_ebtables_legacy) &&
234          EbtablesFilter(name, use_ipv4, false, use_ebtables_legacy);
235 }
236 
EbtablesBroute(const std::string & name,bool use_ipv4,bool add,bool use_ebtables_legacy)237 bool EbtablesBroute(const std::string& name, bool use_ipv4, bool add,
238                     bool use_ebtables_legacy) {
239   std::stringstream ss;
240   // we don't know the name of the ebtables program, but since we're going to
241   // exec this program name, make sure they can only choose between the two
242   // options we currently support, and not something they can overwrite
243   if (use_ebtables_legacy) {
244     ss << kEbtablesLegacyName;
245   } else {
246     ss << kEbtablesName;
247   }
248 
249   ss << " -t broute " << (add ? "-A" : "-D") << " BROUTING -p "
250      << (use_ipv4 ? "ipv4" : "ipv6") << " --in-if " << name << " -j DROP";
251   auto command = ss.str();
252   int status = RunExternalCommand(command);
253 
254   return status == 0;
255 }
256 
EbtablesFilter(const std::string & name,bool use_ipv4,bool add,bool use_ebtables_legacy)257 bool EbtablesFilter(const std::string& name, bool use_ipv4, bool add,
258                     bool use_ebtables_legacy) {
259   std::stringstream ss;
260   if (use_ebtables_legacy) {
261     ss << kEbtablesLegacyName;
262   } else {
263     ss << kEbtablesName;
264   }
265 
266   ss << " -t filter " << (add ? "-A" : "-D") << " FORWARD -p "
267      << (use_ipv4 ? "ipv4" : "ipv6") << " --out-if " << name << " -j DROP";
268   auto command = ss.str();
269   int status = RunExternalCommand(command);
270 
271   return status == 0;
272 }
273 
LinkTapToBridge(const std::string & tap_name,const std::string & bridge_name)274 bool LinkTapToBridge(const std::string& tap_name,
275                      const std::string& bridge_name) {
276   std::stringstream ss;
277   ss << "ip link set dev " << tap_name << " master " << bridge_name;
278   auto command = ss.str();
279   int status = RunExternalCommand(command);
280 
281   return status == 0;
282 }
283 
CreateTap(const std::string & name)284 bool CreateTap(const std::string& name) {
285   LOG(INFO) << "Attempt to create tap interface: " << name;
286   if (!AddTapIface(name)) {
287     LOG(WARNING) << "Failed to create tap interface: " << name;
288     return false;
289   }
290 
291   if (!BringUpIface(name)) {
292     LOG(WARNING) << "Failed to bring up tap interface: " << name;
293     DeleteIface(name);
294     return false;
295   }
296 
297   return true;
298 }
299 
DeleteIface(const std::string & name)300 bool DeleteIface(const std::string& name) {
301   std::stringstream ss;
302   ss << "ip link delete " << name;
303   auto link_command = ss.str();
304   LOG(INFO) << "Delete tap interface: " << link_command;
305   int status = RunExternalCommand(link_command);
306 
307   return status == 0;
308 }
309 
DestroyIface(const std::string & name)310 bool DestroyIface(const std::string& name) {
311   if (!ShutdownIface(name)) {
312     LOG(WARNING) << "Failed to shutdown tap interface: " << name;
313     // the interface might have already shutdown ... so ignore and try to remove
314     // the interface. In the future we could read from the pipe and handle this
315     // case more elegantly
316   }
317 
318   if (!DeleteIface(name)) {
319     LOG(WARNING) << "Failed to delete tap interface: " << name;
320     return false;
321   }
322 
323   return true;
324 }
325 
GetUserName(uid_t uid)326 std::optional<std::string> GetUserName(uid_t uid) {
327   passwd* pw = getpwuid(uid);
328   if (pw) {
329     std::string ret(pw->pw_name);
330     return ret;
331   }
332   return std::nullopt;
333 }
334 
CreateBridge(const std::string & name)335 bool CreateBridge(const std::string& name) {
336   std::stringstream ss;
337   ss << "ip link add name " << name
338      << " type bridge forward_delay 0 stp_state 0";
339 
340   auto command = ss.str();
341   LOG(INFO) << "create bridge: " << command;
342   int status = RunExternalCommand(command);
343 
344   if (status != 0) {
345     return false;
346   }
347 
348   return BringUpIface(name);
349 }
350 
DestroyBridge(const std::string & name)351 bool DestroyBridge(const std::string& name) { return DeleteIface(name); }
352 
SetupBridgeGateway(const std::string & bridge_name,const std::string & ipaddr)353 bool SetupBridgeGateway(const std::string& bridge_name,
354                         const std::string& ipaddr) {
355   GatewayConfig config{false, false, false};
356   auto gateway = ipaddr + ".1";
357   auto netmask = "/24";
358   auto network = ipaddr + ".0" + netmask;
359   auto dhcp_range = ipaddr + ".2," + ipaddr + ".255";
360 
361   if (!AddGateway(bridge_name, gateway, netmask)) {
362     return false;
363   }
364 
365   config.has_gateway = true;
366 
367   if (StartDnsmasq(bridge_name, gateway, dhcp_range)) {
368     CleanupBridgeGateway(bridge_name, ipaddr, config);
369     return false;
370   }
371 
372   config.has_dnsmasq = true;
373 
374   auto ret = IptableConfig(network, true);
375   if (!ret) {
376     CleanupBridgeGateway(bridge_name, ipaddr, config);
377     LOG(WARNING) << "Failed to setup ip tables";
378   }
379 
380   return ret;
381 }
382 
CleanupBridgeGateway(const std::string & name,const std::string & ipaddr,const GatewayConfig & config)383 void CleanupBridgeGateway(const std::string& name, const std::string& ipaddr,
384                           const GatewayConfig& config) {
385   auto gateway = ipaddr + ".1";
386   auto netmask = "/24";
387   auto network = ipaddr + ".0" + netmask;
388   auto dhcp_range = ipaddr + ".2," + ipaddr + ".255";
389 
390   if (config.has_iptable) {
391     IptableConfig(network, false);
392   }
393 
394   if (config.has_dnsmasq) {
395     StopDnsmasq(name);
396   }
397 
398   if (config.has_gateway) {
399     DestroyGateway(name, gateway, netmask);
400   }
401 }
402 
StartDnsmasq(const std::string & bridge_name,const std::string & gateway,const std::string & dhcp_range)403 bool StartDnsmasq(const std::string& bridge_name, const std::string& gateway,
404                   const std::string& dhcp_range) {
405   auto dns_servers = "8.8.8.8,8.8.4.4";
406   auto dns6_servers = "2001:4860:4860::8888,2001:4860:4860::8844";
407   std::stringstream ss;
408 
409   // clang-format off
410   ss <<
411   "dnsmasq"
412     " --port=0"
413     " --strict-order"
414     " --except-interface=lo"
415     " --interface=" << bridge_name <<
416     " --listen-address=" << gateway <<
417     " --bind-interfaces"
418     " --dhcp-range=" << dhcp_range <<
419     " --dhcp-option=\"option:dns-server," << dns_servers << "\""
420     " --dhcp-option=\"option6:dns-server," << dns6_servers << "\""
421     " --conf-file=\"\""
422     " --pid-file=/var/run/cuttlefish-dnsmasq-" << bridge_name << ".pid"
423     " --dhcp-leasefile=/var/run/cuttlefish-dnsmasq-" << bridge_name << ".leases"
424     " --dhcp-no-override ";
425   // clang-format on
426 
427   auto command = ss.str();
428   LOG(INFO) << "start_dnsmasq: " << command;
429   int status = RunExternalCommand(command);
430 
431   return status == 0;
432 }
433 
StopDnsmasq(const std::string & name)434 bool StopDnsmasq(const std::string& name) {
435   std::ifstream file;
436   std::string filename = "/var/run/cuttlefish-dnsmasq-" + name + ".pid";
437   LOG(INFO) << "stopping dsnmasq for interface: " << name;
438   file.open(filename);
439   if (file.is_open()) {
440     LOG(INFO) << "dnsmasq file:" << filename
441               << " could not be opened, assume dnsmaq has already stopped";
442     return true;
443   }
444 
445   std::string pid;
446   file >> pid;
447   file.close();
448   std::string command = "kill " + pid;
449   int status = RunExternalCommand(command);
450   auto ret = (status == 0);
451 
452   if (ret) {
453     LOG(INFO) << "dsnmasq for:" << name << "successfully stopped";
454   } else {
455     LOG(WARNING) << "Failed to stop dsnmasq for:" << name;
456   }
457   return ret;
458 }
459 
IptableConfig(const std::string & network,bool add)460 bool IptableConfig(const std::string& network, bool add) {
461   std::stringstream ss;
462   ss << "iptables -t nat " << (add ? "-A" : "-D") << " POSTROUTING -s "
463      << network << " -j MASQUERADE";
464 
465   auto command = ss.str();
466   LOG(INFO) << "iptable_config: " << command;
467   int status = RunExternalCommand(command);
468 
469   return status == 0;
470 }
471 
CreateEthernetBridgeIface(const std::string & name,const std::string & ipaddr)472 bool CreateEthernetBridgeIface(const std::string& name,
473                                const std::string& ipaddr) {
474   if (!CreateBridge(name)) {
475     return false;
476   }
477 
478   if (!SetupBridgeGateway(name, ipaddr)) {
479     DestroyBridge(name);
480     return false;
481   }
482 
483   return true;
484 }
485 
DestroyEthernetBridgeIface(const std::string & name,const std::string & ipaddr)486 bool DestroyEthernetBridgeIface(const std::string& name,
487                                 const std::string& ipaddr) {
488   GatewayConfig config{true, true, true};
489 
490   // Don't need to check if removing some part of the config failed, we need to
491   // remove the entire interface, so just ignore any error until the end
492   CleanupBridgeGateway(name, ipaddr, config);
493 
494   return DestroyBridge(name);
495 }
496 
497 }  // namespace cuttlefish
498