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