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