blob: 5a25c989297cb585bcfba2fd98fdf065ea2d0544 [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
7#include <vector>
8
9#include <base/bind.h>
10#include "base/notreached.h"
11#include <base/process/launch.h>
12#include <base/threading/thread_task_runner_handle.h>
13#include <chromeos/scoped_minijail.h>
Garrick Evans48c84ef2021-01-28 11:29:42 +090014#include <shill/dbus-constants.h>
Garrick Evans066dc2c2020-12-10 10:43:55 +090015
16namespace dns_proxy {
17namespace {
18
19constexpr int kSubprocessRestartDelayMs = 900;
20constexpr char kSeccompPolicyPath[] =
21 "/usr/share/policy/dns-proxy-seccomp.policy";
22
23} // namespace
24
25Controller::Controller(const std::string& progname) : progname_(progname) {
26 default_proxy_deps_ = std::make_unique<DefaultProxyDeps>(base::Bind(
27 &Controller::EvalDefaultProxyDeps, weak_factory_.GetWeakPtr()));
28}
29
30Controller::~Controller() {
31 for (const auto& p : proxies_)
32 Kill(p);
33}
34
35int Controller::OnInit() {
36 LOG(INFO) << "Starting DNS Proxy service";
37
38 // Handle subprocess lifecycle.
39 process_reaper_.Register(this);
40
41 /// Run after Daemon::OnInit()
42 base::ThreadTaskRunnerHandle::Get()->PostTask(
43 FROM_HERE, base::Bind(&Controller::Setup, weak_factory_.GetWeakPtr()));
44 return DBusDaemon::OnInit();
45}
46
47void Controller::OnShutdown(int*) {
48 LOG(INFO) << "Stopping DNS Proxy service";
49}
50
51void Controller::Setup() {
52 shill_.reset(new shill::Client(bus_));
53 shill_->Init();
54 shill_->RegisterDefaultServiceChangedHandler(base::BindRepeating(
55 &Controller::OnDefaultServiceChanged, weak_factory_.GetWeakPtr()));
56
57 patchpanel_ = patchpanel::Client::New();
58 CHECK(patchpanel_) << "Failed to initialize patchpanel client";
59 patchpanel_->RegisterOnAvailableCallback(base::BindRepeating(
60 &Controller::OnPatchpanelReady, weak_factory_.GetWeakPtr()));
61
62 RunProxy(Proxy::Type::kSystem);
63}
64
65void Controller::OnPatchpanelReady(bool success) {
66 CHECK(success) << "Failed to connect to patchpanel";
67 patchpanel_->RegisterNetworkDeviceChangedSignalHandler(base::BindRepeating(
68 &Controller::OnVirtualDeviceChanged, weak_factory_.GetWeakPtr()));
69
70 // Process the current set of patchpanel devices and launch any required
71 // proxy processes.
72 for (const auto& d : patchpanel_->GetDevices())
73 VirtualDeviceAdded(d);
74}
75
76void Controller::RunProxy(Proxy::Type type, const std::string& ifname) {
77 ProxyProc proc(type, ifname);
78 auto it = proxies_.find(proc);
79 if (it != proxies_.end()) {
80 return;
81 }
82
83 ScopedMinijail jail(minijail_new());
84 minijail_namespace_net(jail.get());
85 minijail_no_new_privs(jail.get());
86 minijail_use_seccomp_filter(jail.get());
87 minijail_parse_seccomp_filters(jail.get(), kSeccompPolicyPath);
88 minijail_forward_signals(jail.get());
89 minijail_reset_signal_mask(jail.get());
90 minijail_reset_signal_handlers(jail.get());
91 minijail_run_as_init(jail.get());
92
93 std::vector<char*> argv;
94 const std::string flag_t = "--t=" + std::string(Proxy::TypeToString(type));
95 argv.push_back(const_cast<char*>(progname_.c_str()));
96 argv.push_back(const_cast<char*>(flag_t.c_str()));
97 if (!ifname.empty()) {
98 const std::string flag_if = "--i=" + ifname;
99 argv.push_back(const_cast<char*>(flag_if.c_str()));
100 }
101 argv.push_back(nullptr);
102
103 pid_t pid;
104 if (minijail_run_pid(jail.get(), argv[0], argv.data(), &pid) != 0) {
105 LOG(DFATAL) << "Failed to launch process for proxy " << proc;
106 return;
107 }
108 proc.pid = pid;
109 LOG(INFO) << "Launched process for proxy " << proc;
110
111 if (!process_reaper_.WatchForChild(
112 FROM_HERE, pid,
113 base::Bind(&Controller::OnProxyExit, weak_factory_.GetWeakPtr(),
114 pid))) {
115 LOG(ERROR) << "Failed to watch process for proxy " << proc
116 << " - did it crash after launch?";
117 return;
118 }
119
120 proxies_.emplace(proc);
121}
122
123void Controller::KillProxy(Proxy::Type type, const std::string& ifname) {
124 auto it = proxies_.find(ProxyProc(type, ifname));
125 if (it != proxies_.end()) {
126 Kill(*it);
127 proxies_.erase(it);
128 }
129}
130
131void Controller::Kill(const ProxyProc& proc) {
Garrick Evans48c84ef2021-01-28 11:29:42 +0900132 EvalProxyExit(proc);
Garrick Evans066dc2c2020-12-10 10:43:55 +0900133 process_reaper_.ForgetChild(proc.pid);
134 int rc = kill(proc.pid, SIGTERM);
135 if (rc < 0 && rc != ESRCH)
136 LOG(ERROR) << "Failed to kill process for proxy " << proc;
137}
138
139void Controller::OnProxyExit(pid_t pid, const siginfo_t& siginfo) {
140 process_reaper_.ForgetChild(pid);
141
142 // There will only ever be a handful of entries in this map so a linear scan
143 // will be trivial.
144 ProxyProc proc;
145 bool found = false;
146 for (auto it = proxies_.begin(); it != proxies_.end(); ++it) {
147 if (it->pid == pid) {
148 proc = *it;
149 proxies_.erase(it);
150 found = true;
151 break;
152 }
153 }
154 if (!found) {
155 LOG(ERROR) << "Unexpected process (" << pid << ") exit signal received";
156 return;
157 }
158
Garrick Evans48c84ef2021-01-28 11:29:42 +0900159 EvalProxyExit(proc);
160
Garrick Evans066dc2c2020-12-10 10:43:55 +0900161 switch (siginfo.si_code) {
162 case CLD_EXITED:
163 case CLD_DUMPED:
164 case CLD_KILLED:
165 case CLD_TRAPPED:
166 LOG(ERROR) << "Process for proxy [" << proc
167 << " was unexpectedly killed (" << siginfo.si_code << ":"
168 << siginfo.si_status << ") - attempting to restart";
169
170 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
171 FROM_HERE,
172 base::Bind(&Controller::RunProxy, weak_factory_.GetWeakPtr(),
173 proc.opts.type, proc.opts.ifname),
174 base::TimeDelta::FromMilliseconds(kSubprocessRestartDelayMs));
175 break;
176
177 case CLD_STOPPED:
178 LOG(WARNING) << "Process for proxy " << proc
179 << " was unexpectedly stopped";
180 break;
181
182 case CLD_CONTINUED:
183 LOG(WARNING) << "Process for proxy " << proc << " has continued";
184 break;
185
186 default:
187 NOTREACHED();
188 }
189}
190
Garrick Evans48c84ef2021-01-28 11:29:42 +0900191void Controller::EvalProxyExit(const ProxyProc& proc) {
192 if (proc.opts.type != Proxy::Type::kSystem)
193 return;
194
195 // Ensure the system proxy address is cleared from shill.
196 brillo::ErrorPtr error;
197 if (!shill_->ManagerProperties()->Set(shill::kDNSProxyIPv4AddressProperty, "",
198 &error))
199 LOG(WARNING) << "Failed to clear shill dns-proxy property for " << proc
200 << ": " << error->GetMessage();
201}
202
Garrick Evans066dc2c2020-12-10 10:43:55 +0900203void Controller::EvalDefaultProxyDeps(bool has_deps) {
204 has_deps ? RunProxy(Proxy::Type::kDefault) : KillProxy(Proxy::Type::kDefault);
205}
206
207void Controller::OnDefaultServiceChanged(const std::string& type) {
208 // If the default service is lost, |type| will be empty. In this case, we can
209 // still safely clear the VPN bit since when it reconnects, if the VPN is
210 // still up, the bit will be reset here.
211 default_proxy_deps_->vpn_on(type == shill::kTypeVPN);
212}
213
214void Controller::OnVirtualDeviceChanged(
215 const patchpanel::NetworkDeviceChangedSignal& signal) {
216 switch (signal.event()) {
217 case patchpanel::NetworkDeviceChangedSignal::DEVICE_ADDED:
218 VirtualDeviceAdded(signal.device());
219 break;
220 case patchpanel::NetworkDeviceChangedSignal::DEVICE_REMOVED:
221 VirtualDeviceRemoved(signal.device());
222 break;
223 default:
224 NOTREACHED();
225 }
226}
227
228void Controller::VirtualDeviceAdded(const patchpanel::NetworkDevice& device) {
229 switch (device.guest_type()) {
230 case patchpanel::NetworkDevice::TERMINA_VM:
231 case patchpanel::NetworkDevice::PLUGIN_VM:
232 default_proxy_deps_->guest_up(device.ifname());
233 break;
234 default:
235 break;
236 }
237}
238
239void Controller::VirtualDeviceRemoved(const patchpanel::NetworkDevice& device) {
240 switch (device.guest_type()) {
241 case patchpanel::NetworkDevice::TERMINA_VM:
242 case patchpanel::NetworkDevice::PLUGIN_VM:
243 default_proxy_deps_->guest_down(device.ifname());
244 break;
245 default:
246 break;
247 }
248}
249
250} // namespace dns_proxy