Garrick Evans | 066dc2c | 2020-12-10 10:43:55 +0900 | [diff] [blame] | 1 | // 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 Evans | 48c84ef | 2021-01-28 11:29:42 +0900 | [diff] [blame] | 14 | #include <shill/dbus-constants.h> |
Garrick Evans | 066dc2c | 2020-12-10 10:43:55 +0900 | [diff] [blame] | 15 | |
| 16 | namespace dns_proxy { |
| 17 | namespace { |
| 18 | |
| 19 | constexpr int kSubprocessRestartDelayMs = 900; |
| 20 | constexpr char kSeccompPolicyPath[] = |
| 21 | "/usr/share/policy/dns-proxy-seccomp.policy"; |
| 22 | |
| 23 | } // namespace |
| 24 | |
| 25 | Controller::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 | |
| 30 | Controller::~Controller() { |
| 31 | for (const auto& p : proxies_) |
| 32 | Kill(p); |
| 33 | } |
| 34 | |
| 35 | int 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 | |
| 47 | void Controller::OnShutdown(int*) { |
| 48 | LOG(INFO) << "Stopping DNS Proxy service"; |
| 49 | } |
| 50 | |
| 51 | void 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 | |
| 65 | void 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 | |
| 76 | void 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 | |
| 123 | void 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 | |
| 131 | void Controller::Kill(const ProxyProc& proc) { |
Garrick Evans | 48c84ef | 2021-01-28 11:29:42 +0900 | [diff] [blame] | 132 | EvalProxyExit(proc); |
Garrick Evans | 066dc2c | 2020-12-10 10:43:55 +0900 | [diff] [blame] | 133 | 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 | |
| 139 | void 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 Evans | 48c84ef | 2021-01-28 11:29:42 +0900 | [diff] [blame] | 159 | EvalProxyExit(proc); |
| 160 | |
Garrick Evans | 066dc2c | 2020-12-10 10:43:55 +0900 | [diff] [blame] | 161 | 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 Evans | 48c84ef | 2021-01-28 11:29:42 +0900 | [diff] [blame] | 191 | void 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 Evans | 066dc2c | 2020-12-10 10:43:55 +0900 | [diff] [blame] | 203 | void Controller::EvalDefaultProxyDeps(bool has_deps) { |
| 204 | has_deps ? RunProxy(Proxy::Type::kDefault) : KillProxy(Proxy::Type::kDefault); |
| 205 | } |
| 206 | |
| 207 | void 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 | |
| 214 | void 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 | |
| 228 | void 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 | |
| 239 | void 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 |