blob: 207f1f24ffb6584dd570a1498e277a9fef2f1068 [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>
13#include "base/notreached.h"
14#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();
67 shill_->RegisterDefaultServiceChangedHandler(base::BindRepeating(
68 &Controller::OnDefaultServiceChanged, weak_factory_.GetWeakPtr()));
69
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()));
110 if (!ifname.empty()) {
111 const std::string flag_if = "--i=" + ifname;
112 argv.push_back(const_cast<char*>(flag_if.c_str()));
113 }
114 argv.push_back(nullptr);
115
116 pid_t pid;
117 if (minijail_run_pid(jail.get(), argv[0], argv.data(), &pid) != 0) {
118 LOG(DFATAL) << "Failed to launch process for proxy " << proc;
119 return;
120 }
121 proc.pid = pid;
122 LOG(INFO) << "Launched process for proxy " << proc;
123
124 if (!process_reaper_.WatchForChild(
125 FROM_HERE, pid,
126 base::Bind(&Controller::OnProxyExit, weak_factory_.GetWeakPtr(),
127 pid))) {
128 LOG(ERROR) << "Failed to watch process for proxy " << proc
129 << " - did it crash after launch?";
130 return;
131 }
132
133 proxies_.emplace(proc);
134}
135
136void Controller::KillProxy(Proxy::Type type, const std::string& ifname) {
137 auto it = proxies_.find(ProxyProc(type, ifname));
138 if (it != proxies_.end()) {
139 Kill(*it);
140 proxies_.erase(it);
141 }
142}
143
144void Controller::Kill(const ProxyProc& proc) {
Garrick Evans48c84ef2021-01-28 11:29:42 +0900145 EvalProxyExit(proc);
Garrick Evans066dc2c2020-12-10 10:43:55 +0900146 process_reaper_.ForgetChild(proc.pid);
147 int rc = kill(proc.pid, SIGTERM);
148 if (rc < 0 && rc != ESRCH)
149 LOG(ERROR) << "Failed to kill process for proxy " << proc;
150}
151
152void Controller::OnProxyExit(pid_t pid, const siginfo_t& siginfo) {
153 process_reaper_.ForgetChild(pid);
154
155 // There will only ever be a handful of entries in this map so a linear scan
156 // will be trivial.
157 ProxyProc proc;
158 bool found = false;
159 for (auto it = proxies_.begin(); it != proxies_.end(); ++it) {
160 if (it->pid == pid) {
161 proc = *it;
162 proxies_.erase(it);
163 found = true;
164 break;
165 }
166 }
167 if (!found) {
168 LOG(ERROR) << "Unexpected process (" << pid << ") exit signal received";
169 return;
170 }
171
Garrick Evans48c84ef2021-01-28 11:29:42 +0900172 EvalProxyExit(proc);
173
Garrick Evans066dc2c2020-12-10 10:43:55 +0900174 switch (siginfo.si_code) {
175 case CLD_EXITED:
176 case CLD_DUMPED:
177 case CLD_KILLED:
178 case CLD_TRAPPED:
179 LOG(ERROR) << "Process for proxy [" << proc
180 << " was unexpectedly killed (" << siginfo.si_code << ":"
181 << siginfo.si_status << ") - attempting to restart";
182
183 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
184 FROM_HERE,
185 base::Bind(&Controller::RunProxy, weak_factory_.GetWeakPtr(),
186 proc.opts.type, proc.opts.ifname),
187 base::TimeDelta::FromMilliseconds(kSubprocessRestartDelayMs));
188 break;
189
190 case CLD_STOPPED:
191 LOG(WARNING) << "Process for proxy " << proc
192 << " was unexpectedly stopped";
193 break;
194
195 case CLD_CONTINUED:
196 LOG(WARNING) << "Process for proxy " << proc << " has continued";
197 break;
198
199 default:
200 NOTREACHED();
201 }
202}
203
Garrick Evans48c84ef2021-01-28 11:29:42 +0900204void Controller::EvalProxyExit(const ProxyProc& proc) {
205 if (proc.opts.type != Proxy::Type::kSystem)
206 return;
207
208 // Ensure the system proxy address is cleared from shill.
209 brillo::ErrorPtr error;
210 if (!shill_->ManagerProperties()->Set(shill::kDNSProxyIPv4AddressProperty, "",
211 &error))
212 LOG(WARNING) << "Failed to clear shill dns-proxy property for " << proc
213 << ": " << error->GetMessage();
214}
215
Garrick Evans066dc2c2020-12-10 10:43:55 +0900216void Controller::EvalDefaultProxyDeps(bool has_deps) {
217 has_deps ? RunProxy(Proxy::Type::kDefault) : KillProxy(Proxy::Type::kDefault);
218}
219
220void Controller::OnDefaultServiceChanged(const std::string& type) {
221 // If the default service is lost, |type| will be empty. In this case, we can
222 // still safely clear the VPN bit since when it reconnects, if the VPN is
223 // still up, the bit will be reset here.
224 default_proxy_deps_->vpn_on(type == shill::kTypeVPN);
225}
226
227void Controller::OnVirtualDeviceChanged(
228 const patchpanel::NetworkDeviceChangedSignal& signal) {
229 switch (signal.event()) {
230 case patchpanel::NetworkDeviceChangedSignal::DEVICE_ADDED:
231 VirtualDeviceAdded(signal.device());
232 break;
233 case patchpanel::NetworkDeviceChangedSignal::DEVICE_REMOVED:
234 VirtualDeviceRemoved(signal.device());
235 break;
236 default:
237 NOTREACHED();
238 }
239}
240
241void Controller::VirtualDeviceAdded(const patchpanel::NetworkDevice& device) {
242 switch (device.guest_type()) {
243 case patchpanel::NetworkDevice::TERMINA_VM:
244 case patchpanel::NetworkDevice::PLUGIN_VM:
245 default_proxy_deps_->guest_up(device.ifname());
246 break;
247 default:
248 break;
249 }
250}
251
252void Controller::VirtualDeviceRemoved(const patchpanel::NetworkDevice& device) {
253 switch (device.guest_type()) {
254 case patchpanel::NetworkDevice::TERMINA_VM:
255 case patchpanel::NetworkDevice::PLUGIN_VM:
256 default_proxy_deps_->guest_down(device.ifname());
257 break;
258 default:
259 break;
260 }
261}
262
263} // namespace dns_proxy