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> |
| 14 | |
| 15 | namespace dns_proxy { |
| 16 | namespace { |
| 17 | |
| 18 | constexpr int kSubprocessRestartDelayMs = 900; |
| 19 | constexpr char kSeccompPolicyPath[] = |
| 20 | "/usr/share/policy/dns-proxy-seccomp.policy"; |
| 21 | |
| 22 | } // namespace |
| 23 | |
| 24 | Controller::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 | |
| 29 | Controller::~Controller() { |
| 30 | for (const auto& p : proxies_) |
| 31 | Kill(p); |
| 32 | } |
| 33 | |
| 34 | int 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 | |
| 46 | void Controller::OnShutdown(int*) { |
| 47 | LOG(INFO) << "Stopping DNS Proxy service"; |
| 48 | } |
| 49 | |
| 50 | void 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 | |
| 64 | void 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 | |
| 75 | void 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 | |
| 122 | void 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 | |
| 130 | void 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 | |
| 137 | void 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 | |
| 187 | void Controller::EvalDefaultProxyDeps(bool has_deps) { |
| 188 | has_deps ? RunProxy(Proxy::Type::kDefault) : KillProxy(Proxy::Type::kDefault); |
| 189 | } |
| 190 | |
| 191 | void 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 | |
| 198 | void 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 | |
| 212 | void 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 | |
| 223 | void 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 |