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