blob: 0c4486cbf32a7654236c64719e8a1d39c8c1943f [file] [log] [blame]
Garrick Evans066dc2c2020-12-10 10:43:55 +09001// Copyright 2021 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "dns-proxy/controller.h"
6
Jason Jeremy Imanf6080422021-02-08 03:10:18 +09007#include <sys/capability.h>
8#include <sys/prctl.h>
9
Garrick Evans066dc2c2020-12-10 10:43:55 +090010#include <vector>
11
12#include <base/bind.h>
Qijiang Fan886c4692021-02-19 11:54:10 +090013#include <base/notreached.h>
Garrick Evans066dc2c2020-12-10 10:43:55 +090014#include <base/process/launch.h>
15#include <base/threading/thread_task_runner_handle.h>
16#include <chromeos/scoped_minijail.h>
Garrick Evans48c84ef2021-01-28 11:29:42 +090017#include <shill/dbus-constants.h>
Garrick Evans066dc2c2020-12-10 10:43:55 +090018
19namespace dns_proxy {
20namespace {
21
22constexpr int kSubprocessRestartDelayMs = 900;
23constexpr char kSeccompPolicyPath[] =
24 "/usr/share/policy/dns-proxy-seccomp.policy";
25
26} // namespace
27
28Controller::Controller(const std::string& progname) : progname_(progname) {
29 default_proxy_deps_ = std::make_unique<DefaultProxyDeps>(base::Bind(
30 &Controller::EvalDefaultProxyDeps, weak_factory_.GetWeakPtr()));
31}
32
33Controller::~Controller() {
34 for (const auto& p : proxies_)
35 Kill(p);
Garrick Evans9c8797d2021-02-17 21:28:10 +090036
37 if (bus_)
38 bus_->ShutdownAndBlock();
Garrick Evans066dc2c2020-12-10 10:43:55 +090039}
40
41int Controller::OnInit() {
42 LOG(INFO) << "Starting DNS Proxy service";
43
Jason Jeremy Imanf6080422021-02-08 03:10:18 +090044 // Preserve CAP_NET_BIND_SERVICE so the child processes have the capability.
45 // Without the ambient set, file capabilities need to be used.
46 if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_BIND_SERVICE, 0, 0) !=
47 0) {
48 LOG(ERROR) << "Failed to add CAP_NET_BIND_SERVICE to the ambient set";
49 }
50
Garrick Evans066dc2c2020-12-10 10:43:55 +090051 // Handle subprocess lifecycle.
52 process_reaper_.Register(this);
53
54 /// Run after Daemon::OnInit()
55 base::ThreadTaskRunnerHandle::Get()->PostTask(
56 FROM_HERE, base::Bind(&Controller::Setup, weak_factory_.GetWeakPtr()));
57 return DBusDaemon::OnInit();
58}
59
60void Controller::OnShutdown(int*) {
61 LOG(INFO) << "Stopping DNS Proxy service";
62}
63
64void Controller::Setup() {
65 shill_.reset(new shill::Client(bus_));
66 shill_->Init();
Garrick Evans07c86d62021-02-18 09:31:42 +090067 shill_->RegisterDefaultDeviceChangedHandler(base::BindRepeating(
68 &Controller::OnDefaultDeviceChanged, weak_factory_.GetWeakPtr()));
Garrick Evans066dc2c2020-12-10 10:43:55 +090069
70 patchpanel_ = patchpanel::Client::New();
71 CHECK(patchpanel_) << "Failed to initialize patchpanel client";
72 patchpanel_->RegisterOnAvailableCallback(base::BindRepeating(
73 &Controller::OnPatchpanelReady, weak_factory_.GetWeakPtr()));
74
75 RunProxy(Proxy::Type::kSystem);
76}
77
78void Controller::OnPatchpanelReady(bool success) {
79 CHECK(success) << "Failed to connect to patchpanel";
80 patchpanel_->RegisterNetworkDeviceChangedSignalHandler(base::BindRepeating(
81 &Controller::OnVirtualDeviceChanged, weak_factory_.GetWeakPtr()));
82
83 // Process the current set of patchpanel devices and launch any required
84 // proxy processes.
85 for (const auto& d : patchpanel_->GetDevices())
86 VirtualDeviceAdded(d);
87}
88
89void Controller::RunProxy(Proxy::Type type, const std::string& ifname) {
90 ProxyProc proc(type, ifname);
91 auto it = proxies_.find(proc);
92 if (it != proxies_.end()) {
93 return;
94 }
95
96 ScopedMinijail jail(minijail_new());
97 minijail_namespace_net(jail.get());
98 minijail_no_new_privs(jail.get());
99 minijail_use_seccomp_filter(jail.get());
100 minijail_parse_seccomp_filters(jail.get(), kSeccompPolicyPath);
101 minijail_forward_signals(jail.get());
102 minijail_reset_signal_mask(jail.get());
103 minijail_reset_signal_handlers(jail.get());
104 minijail_run_as_init(jail.get());
105
106 std::vector<char*> argv;
107 const std::string flag_t = "--t=" + std::string(Proxy::TypeToString(type));
108 argv.push_back(const_cast<char*>(progname_.c_str()));
109 argv.push_back(const_cast<char*>(flag_t.c_str()));
Garrick Evans19541ba2021-02-18 13:24:03 +0900110 std::string flag_i = "--i=";
Garrick Evans066dc2c2020-12-10 10:43:55 +0900111 if (!ifname.empty()) {
Garrick Evans19541ba2021-02-18 13:24:03 +0900112 flag_i += ifname;
113 argv.push_back(const_cast<char*>(flag_i.c_str()));
Garrick Evans066dc2c2020-12-10 10:43:55 +0900114 }
115 argv.push_back(nullptr);
116
117 pid_t pid;
118 if (minijail_run_pid(jail.get(), argv[0], argv.data(), &pid) != 0) {
119 LOG(DFATAL) << "Failed to launch process for proxy " << proc;
120 return;
121 }
122 proc.pid = pid;
123 LOG(INFO) << "Launched process for proxy " << proc;
124
125 if (!process_reaper_.WatchForChild(
126 FROM_HERE, pid,
127 base::Bind(&Controller::OnProxyExit, weak_factory_.GetWeakPtr(),
128 pid))) {
129 LOG(ERROR) << "Failed to watch process for proxy " << proc
130 << " - did it crash after launch?";
131 return;
132 }
133
134 proxies_.emplace(proc);
135}
136
137void Controller::KillProxy(Proxy::Type type, const std::string& ifname) {
138 auto it = proxies_.find(ProxyProc(type, ifname));
139 if (it != proxies_.end()) {
140 Kill(*it);
141 proxies_.erase(it);
142 }
143}
144
145void Controller::Kill(const ProxyProc& proc) {
Garrick Evans48c84ef2021-01-28 11:29:42 +0900146 EvalProxyExit(proc);
Garrick Evans066dc2c2020-12-10 10:43:55 +0900147 process_reaper_.ForgetChild(proc.pid);
148 int rc = kill(proc.pid, SIGTERM);
149 if (rc < 0 && rc != ESRCH)
150 LOG(ERROR) << "Failed to kill process for proxy " << proc;
151}
152
153void Controller::OnProxyExit(pid_t pid, const siginfo_t& siginfo) {
154 process_reaper_.ForgetChild(pid);
155
156 // There will only ever be a handful of entries in this map so a linear scan
157 // will be trivial.
158 ProxyProc proc;
159 bool found = false;
160 for (auto it = proxies_.begin(); it != proxies_.end(); ++it) {
161 if (it->pid == pid) {
162 proc = *it;
163 proxies_.erase(it);
164 found = true;
165 break;
166 }
167 }
168 if (!found) {
169 LOG(ERROR) << "Unexpected process (" << pid << ") exit signal received";
170 return;
171 }
172
Garrick Evans48c84ef2021-01-28 11:29:42 +0900173 EvalProxyExit(proc);
174
Garrick Evans066dc2c2020-12-10 10:43:55 +0900175 switch (siginfo.si_code) {
176 case CLD_EXITED:
177 case CLD_DUMPED:
178 case CLD_KILLED:
179 case CLD_TRAPPED:
180 LOG(ERROR) << "Process for proxy [" << proc
181 << " was unexpectedly killed (" << siginfo.si_code << ":"
182 << siginfo.si_status << ") - attempting to restart";
183
184 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
185 FROM_HERE,
186 base::Bind(&Controller::RunProxy, weak_factory_.GetWeakPtr(),
187 proc.opts.type, proc.opts.ifname),
188 base::TimeDelta::FromMilliseconds(kSubprocessRestartDelayMs));
189 break;
190
191 case CLD_STOPPED:
192 LOG(WARNING) << "Process for proxy " << proc
193 << " was unexpectedly stopped";
194 break;
195
196 case CLD_CONTINUED:
197 LOG(WARNING) << "Process for proxy " << proc << " has continued";
198 break;
199
200 default:
201 NOTREACHED();
202 }
203}
204
Garrick Evans48c84ef2021-01-28 11:29:42 +0900205void Controller::EvalProxyExit(const ProxyProc& proc) {
206 if (proc.opts.type != Proxy::Type::kSystem)
207 return;
208
209 // Ensure the system proxy address is cleared from shill.
210 brillo::ErrorPtr error;
211 if (!shill_->ManagerProperties()->Set(shill::kDNSProxyIPv4AddressProperty, "",
212 &error))
213 LOG(WARNING) << "Failed to clear shill dns-proxy property for " << proc
214 << ": " << error->GetMessage();
215}
216
Garrick Evans066dc2c2020-12-10 10:43:55 +0900217void Controller::EvalDefaultProxyDeps(bool has_deps) {
218 has_deps ? RunProxy(Proxy::Type::kDefault) : KillProxy(Proxy::Type::kDefault);
219}
220
Garrick Evans07c86d62021-02-18 09:31:42 +0900221void Controller::OnDefaultDeviceChanged(
222 const shill::Client::Device* const device) {
223 // If the default service is lost, |device| will be null. In this case, we can
Garrick Evans066dc2c2020-12-10 10:43:55 +0900224 // still safely clear the VPN bit since when it reconnects, if the VPN is
225 // still up, the bit will be reset here.
Garrick Evans07c86d62021-02-18 09:31:42 +0900226 // Note that all VPN devices now use the kVPN type (so there is no need to
227 // check kPPP or kTunnel).
228 default_proxy_deps_->vpn_on(device && device->type ==
229 shill::Client::Device::Type::kVPN);
Garrick Evans066dc2c2020-12-10 10:43:55 +0900230}
231
232void Controller::OnVirtualDeviceChanged(
233 const patchpanel::NetworkDeviceChangedSignal& signal) {
234 switch (signal.event()) {
235 case patchpanel::NetworkDeviceChangedSignal::DEVICE_ADDED:
236 VirtualDeviceAdded(signal.device());
237 break;
238 case patchpanel::NetworkDeviceChangedSignal::DEVICE_REMOVED:
239 VirtualDeviceRemoved(signal.device());
240 break;
241 default:
242 NOTREACHED();
243 }
244}
245
246void Controller::VirtualDeviceAdded(const patchpanel::NetworkDevice& device) {
247 switch (device.guest_type()) {
248 case patchpanel::NetworkDevice::TERMINA_VM:
249 case patchpanel::NetworkDevice::PLUGIN_VM:
250 default_proxy_deps_->guest_up(device.ifname());
251 break;
Garrick Evans19541ba2021-02-18 13:24:03 +0900252 case patchpanel::NetworkDevice::ARC:
253 case patchpanel::NetworkDevice::ARCVM:
254 RunProxy(Proxy::Type::kARC, device.phys_ifname());
255 break;
Garrick Evans066dc2c2020-12-10 10:43:55 +0900256 default:
257 break;
258 }
259}
260
261void Controller::VirtualDeviceRemoved(const patchpanel::NetworkDevice& device) {
262 switch (device.guest_type()) {
263 case patchpanel::NetworkDevice::TERMINA_VM:
264 case patchpanel::NetworkDevice::PLUGIN_VM:
265 default_proxy_deps_->guest_down(device.ifname());
266 break;
Garrick Evans19541ba2021-02-18 13:24:03 +0900267 case patchpanel::NetworkDevice::ARC:
268 case patchpanel::NetworkDevice::ARCVM:
269 KillProxy(Proxy::Type::kARC, device.phys_ifname());
270 break;
Garrick Evans066dc2c2020-12-10 10:43:55 +0900271 default:
272 break;
273 }
274}
275
276} // namespace dns_proxy