Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2011 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 "vpn-manager/ipsec_manager.h" | |
| 6 | |
| 7 #include <arpa/inet.h> // for inet_ntop and inet_pton | |
| 8 #include <grp.h> | |
| 9 #include <netdb.h> // for getaddrinfo | |
| 10 #include <sys/types.h> | |
| 11 #include <sys/wait.h> | |
| 12 #include <unistd.h> | |
| 13 | |
| 14 #include <string> | |
| 15 #include <vector> | |
| 16 | |
| 17 #include "base/eintr_wrapper.h" | |
| 18 #include "base/file_util.h" | |
| 19 #include "base/logging.h" | |
| 20 #include "base/string_util.h" | |
| 21 #include "chromeos/process.h" | |
| 22 #include "gflags/gflags.h" | |
| 23 | |
| 24 #pragma GCC diagnostic ignored "-Wstrict-aliasing" | |
| 25 DEFINE_int32(ipsec_timeout, 10, "timeout for ipsec to be established"); | |
| 26 DEFINE_string(leftprotoport, "17/1701", "client protocol/port"); | |
| 27 DEFINE_bool(pfs, false, "pfs"); | |
| 28 DEFINE_bool(rekey, false, "rekey"); | |
| 29 DEFINE_string(rightprotoport, "17/1701", "server protocol/port"); | |
| 30 #pragma GCC diagnostic error "-Wstrict-aliasing" | |
| 31 | |
| 32 const char kIpsecConnectionName[] = "ipsec_managed"; | |
| 33 const char kIpsecGroupName[] = "ipsec"; | |
| 34 const char kIpsecRunPath[] = "/var/run/ipsec"; | |
| 35 const char kIpsecUpFile[] = "/var/run/ipsec/up"; | |
| 36 const char kIpsecServiceName[] = "ipsec"; | |
| 37 const char kStarterPidFile[] = "/var/run/starter.pid"; | |
| 38 const mode_t kIpsecRunPathMode = (S_IRUSR | S_IWUSR | S_IXUSR | | |
| 39 S_IRGRP | S_IWGRP | S_IXGRP); | |
| 40 const char kStatefulContainer[] = "/mnt/stateful_partition/etc"; | |
| 41 | |
| 42 // Give IPsec layer 2 seconds to shut down before killing it. | |
| 43 const int kTermTimeout = 2; | |
| 44 | |
| 45 using ::chromeos::Process; | |
| 46 using ::chromeos::ProcessImpl; | |
| 47 | |
| 48 IpsecManager::IpsecManager() | |
| 49 : ServiceManager(kIpsecServiceName), | |
| 50 force_local_address_(NULL), | |
| 51 output_fd_(-1), | |
| 52 ike_version_(0), | |
| 53 ipsec_group_(0), | |
| 54 stateful_container_(kStatefulContainer), | |
| 55 ipsec_run_path_(kIpsecRunPath), | |
| 56 ipsec_up_file_(kIpsecUpFile), | |
| 57 starter_pid_file_(kStarterPidFile), | |
| 58 starter_(new ProcessImpl) { | |
| 59 } | |
| 60 | |
| 61 bool IpsecManager::Initialize(int ike_version, | |
| 62 const std::string& remote_address, | |
| 63 const std::string& psk_file, | |
| 64 const std::string& server_ca_file, | |
| 65 const std::string& client_key_file, | |
| 66 const std::string& client_cert_file) { | |
| 67 if (remote_address.empty()) { | |
| 68 LOG(ERROR) << "Missing remote address to IPsec layer"; | |
| 69 return false; | |
| 70 } | |
| 71 remote_address_ = remote_address; | |
| 72 | |
| 73 if (psk_file.empty()) { | |
| 74 if (server_ca_file.empty() && client_key_file.empty() && | |
| 75 client_cert_file.empty()) { | |
| 76 LOG(ERROR) << "Must specify either PSK or certificates for IPsec layer"; | |
| 77 return false; | |
| 78 } | |
| 79 | |
| 80 // Must be a certificate based connection. | |
| 81 if (!file_util::PathExists(FilePath(server_ca_file))) { | |
| 82 LOG(ERROR) << "Invalid server CA file for IPsec layer: " | |
| 83 << server_ca_file; | |
| 84 return false; | |
| 85 } | |
| 86 server_ca_file_ = server_ca_file; | |
| 87 | |
| 88 if (!file_util::PathExists(FilePath(client_key_file))) { | |
| 89 LOG(ERROR) << "Invalid client key file for IPsec layer: " | |
| 90 << client_key_file; | |
| 91 return false; | |
| 92 } | |
| 93 client_key_file_ = client_key_file; | |
| 94 | |
| 95 if (!file_util::PathExists(FilePath(client_cert_file))) { | |
| 96 LOG(ERROR) << "Invalid client certificate file for IPsec layer: " | |
| 97 << client_key_file; | |
| 98 return false; | |
| 99 } | |
| 100 client_cert_file_ = client_cert_file; | |
| 101 } else { | |
| 102 if (!server_ca_file.empty() || | |
| 103 !client_key_file.empty() || | |
| 104 !client_cert_file.empty()) { | |
| 105 LOG(ERROR) << "Specified both PSK and certificates for IPsec layer"; | |
| 106 return false; | |
| 107 } | |
| 108 if (!file_util::PathExists(FilePath(psk_file))) { | |
| 109 LOG(ERROR) << "Invalid PSK file for IPsec layer: " << psk_file; | |
| 110 return false; | |
| 111 } | |
| 112 psk_file_ = psk_file; | |
| 113 } | |
| 114 | |
| 115 if (ike_version != 1 && ike_version != 2) { | |
| 116 LOG(ERROR) << "Unsupported IKE version" << ike_version; | |
| 117 return false; | |
| 118 } | |
| 119 ike_version_ = ike_version; | |
| 120 | |
| 121 file_util::Delete(FilePath(kIpsecUpFile), false); | |
| 122 | |
| 123 return true; | |
| 124 } | |
| 125 | |
| 126 bool IpsecManager::GetLocalAddressForRemote(const std::string& remote_address_te xt, | |
|
petkov
2011/03/11 19:12:53
80 chars
kmixter1
2011/03/11 20:53:04
Done.
| |
| 127 std::string* local_address_text) { | |
| 128 static const char kService[] = "80"; | |
| 129 if (force_local_address_ != NULL) { | |
| 130 *local_address_text = force_local_address_; | |
| 131 return true; | |
| 132 } | |
| 133 struct addrinfo *remote_address; | |
| 134 int s = getaddrinfo(remote_address_text.c_str(), kService, NULL, | |
| 135 &remote_address); | |
| 136 if (s != 0) { | |
| 137 LOG(ERROR) << "getaddrinfo failed: " << gai_strerror(s); | |
| 138 return false; | |
| 139 } | |
| 140 int sock = HANDLE_EINTR(socket(AF_INET, SOCK_DGRAM, 0)); | |
| 141 if (sock < 0) { | |
| 142 LOG(ERROR) << "Unable to create socket"; | |
| 143 return false; | |
| 144 } | |
| 145 if (HANDLE_EINTR( | |
| 146 connect(sock, remote_address->ai_addr, sizeof(sockaddr))) != 0) { | |
| 147 LOG(ERROR) << "Unable to connect"; | |
| 148 HANDLE_EINTR(close(sock)); | |
| 149 return false; | |
| 150 } | |
| 151 bool result = false; | |
| 152 struct sockaddr local_address; | |
| 153 socklen_t addr_len = sizeof(local_address); | |
| 154 char str[INET6_ADDRSTRLEN] = { 0 }; | |
| 155 if (getsockname(sock, &local_address, &addr_len) != 0) { | |
| 156 int saved_errno = errno; | |
| 157 LOG(ERROR) << "getsockname failed on socket connecting to " | |
| 158 << remote_address_text << ": " << saved_errno; | |
| 159 goto error_label; | |
|
petkov
2011/03/11 19:12:53
You could define a ScopedSocketCloser and ScopedAd
kmixter1
2011/03/11 20:53:04
Noted. If I need to write this kind of code again
| |
| 160 } | |
| 161 // convert local_address to local_address_text. | |
| 162 switch (local_address.sa_family) { | |
| 163 case AF_INET: | |
| 164 if (!inet_ntop(AF_INET, &reinterpret_cast<sockaddr_in*>( | |
| 165 &local_address)->sin_addr, str, INET6_ADDRSTRLEN)) { | |
| 166 LOG(ERROR) << "inet_ntop failed on " << remote_address_text; | |
| 167 goto error_label; | |
| 168 } | |
| 169 break; | |
| 170 case AF_INET6: | |
| 171 if (!inet_ntop(AF_INET6, &reinterpret_cast<sockaddr_in6*>( | |
| 172 &local_address)->sin6_addr, str, INET6_ADDRSTRLEN)) { | |
| 173 LOG(ERROR) << "inet_ntop failed on " << remote_address_text; | |
| 174 goto error_label; | |
| 175 } | |
| 176 break; | |
| 177 default: | |
| 178 LOG(ERROR) << "Unknown address family converting " << remote_address_text; | |
| 179 goto error_label; | |
| 180 } | |
| 181 *local_address_text = str; | |
| 182 LOG(INFO) << "Remote address " << remote_address_text << " has local address " | |
| 183 << *local_address_text; | |
| 184 result = true; | |
| 185 | |
| 186 error_label: | |
| 187 HANDLE_EINTR(close(sock)); | |
| 188 freeaddrinfo(remote_address); | |
| 189 return result; | |
| 190 } | |
| 191 | |
| 192 bool IpsecManager::FormatPsk(const FilePath& input_file, | |
| 193 std::string* formatted) { | |
| 194 std::string psk; | |
| 195 if (!file_util::ReadFileToString(input_file, &psk)) { | |
| 196 LOG(ERROR) << "Unable to read PSK from " << input_file.value(); | |
| 197 return false; | |
| 198 } | |
| 199 std::string local_address; | |
| 200 if (!GetLocalAddressForRemote(remote_address_, &local_address)) { | |
| 201 LOG(ERROR) << "Local IP address could not be determined for PSK mode"; | |
| 202 return false; | |
| 203 } | |
| 204 TrimWhitespaceASCII(psk, TRIM_TRAILING, &psk); | |
| 205 *formatted = | |
| 206 StringPrintf("%s %s : PSK \"%s\"\n", local_address.c_str(), | |
| 207 remote_address_.c_str(), psk.c_str()); | |
| 208 return true; | |
| 209 } | |
| 210 | |
| 211 void IpsecManager::KillCurrentlyRunning() { | |
| 212 if (!file_util::PathExists(FilePath(starter_pid_file_))) | |
| 213 return; | |
| 214 starter_->ResetPidByFile(starter_pid_file_); | |
| 215 if (Process::ProcessExists(starter_->pid())) | |
| 216 starter_->Reset(0); | |
| 217 else | |
| 218 starter_->Release(); | |
| 219 file_util::Delete(FilePath(starter_pid_file_), false); | |
| 220 } | |
| 221 | |
| 222 bool IpsecManager::StartStarter() { | |
| 223 KillCurrentlyRunning(); | |
| 224 LOG(INFO) << "Starting starter"; | |
| 225 starter_->AddArg(IPSEC_STARTER); | |
| 226 starter_->AddArg("--nofork"); | |
| 227 starter_->RedirectUsingPipe(STDERR_FILENO, false); | |
| 228 if (!starter_->Start()) { | |
| 229 LOG(ERROR) << "Starter did not start successfully"; | |
| 230 return false; | |
| 231 } | |
| 232 output_fd_ = starter_->GetPipe(STDERR_FILENO); | |
| 233 pid_t starter_pid = starter_->pid(); | |
| 234 LOG(INFO) << "Starter started as pid " << starter_pid; | |
| 235 ipsec_prefix_ = StringPrintf("ipsec[%d]: ", starter_pid); | |
| 236 return true; | |
| 237 } | |
| 238 | |
| 239 inline void AppendBoolSetting(std::string* config, const char* key, | |
| 240 bool value) { | |
| 241 config->append(StringPrintf("\t%s=%s\n", key, value ? "yes" : "no")); | |
| 242 } | |
| 243 | |
| 244 inline void AppendStringSetting(std::string* config, const char* key, | |
| 245 const std::string& value) { | |
| 246 config->append(StringPrintf("\t%s=%s\n", key, value.c_str())); | |
| 247 } | |
| 248 | |
| 249 inline void AppendIntSetting(std::string* config, const char* key, | |
| 250 int value) { | |
| 251 config->append(StringPrintf("\t%s=%d\n", key, value)); | |
| 252 } | |
| 253 | |
| 254 std::string IpsecManager::FormatStarterConfigFile() { | |
| 255 std::string config; | |
| 256 config.append("config setup\n"); | |
| 257 if (ike_version_ == 1) { | |
| 258 AppendBoolSetting(&config, "charonstart", false); | |
| 259 } else { | |
| 260 AppendBoolSetting(&config, "plutostart", false); | |
| 261 } | |
| 262 config.append("conn managed\n"); | |
| 263 AppendStringSetting(&config, "keyexchange", | |
| 264 ike_version_ == 1 ? "ikev1" : "ikev2"); | |
| 265 if (!psk_file_.empty()) AppendStringSetting(&config, "authby", "psk"); | |
| 266 AppendBoolSetting(&config, "pfs", FLAGS_pfs); | |
| 267 AppendBoolSetting(&config, "rekey", FLAGS_rekey); | |
| 268 AppendStringSetting(&config, "left", "%defaultroute"); | |
| 269 AppendStringSetting(&config, "leftprotoport", FLAGS_leftprotoport); | |
| 270 AppendStringSetting(&config, "leftupdown", IPSEC_UPDOWN); | |
| 271 AppendStringSetting(&config, "right", remote_address_); | |
| 272 AppendStringSetting(&config, "rightprotoport", FLAGS_rightprotoport); | |
| 273 AppendStringSetting(&config, "auto", "start"); | |
| 274 return config; | |
| 275 } | |
| 276 | |
| 277 bool IpsecManager::SetIpsecGroup(const FilePath& file_path) { | |
| 278 return chown(file_path.value().c_str(), getuid(), ipsec_group_) == 0; | |
| 279 } | |
| 280 | |
| 281 bool IpsecManager::WriteConfigFiles() { | |
| 282 // We need to keep secrets in /mnt/stateful_partition/etc for now | |
| 283 // because pluto loses permissions to /home/chronos before it tries | |
| 284 // reading secrets. | |
| 285 // TODO(kmixter): write this via a fifo. | |
| 286 FilePath secrets_path_ = FilePath(stateful_container_). | |
| 287 Append("ipsec.secrets"); | |
| 288 file_util::Delete(secrets_path_, false); | |
| 289 if (!psk_file_.empty()) { | |
| 290 std::string formatted; | |
| 291 if (!FormatPsk(FilePath(psk_file_), &formatted)) { | |
| 292 LOG(ERROR) << "Unable to create secrets contents"; | |
| 293 return false; | |
| 294 } | |
| 295 if (!file_util::WriteFile(secrets_path_, formatted.c_str(), | |
| 296 formatted.length()) || | |
| 297 !SetIpsecGroup(secrets_path_)) { | |
| 298 LOG(ERROR) << "Unable to write secrets file " << secrets_path_.value(); | |
| 299 return false; | |
| 300 } | |
| 301 } else { | |
| 302 LOG(FATAL) << "Certificate mode not yet implemented"; | |
| 303 } | |
| 304 FilePath starter_config_path = temp_path()->Append("ipsec.conf"); | |
| 305 std::string starter_config = FormatStarterConfigFile(); | |
| 306 if (!file_util::WriteFile(starter_config_path, starter_config.c_str(), | |
| 307 starter_config.size()) || | |
| 308 !SetIpsecGroup(starter_config_path)) { | |
| 309 LOG(ERROR) << "Unable to write ipsec config files"; | |
| 310 return false; | |
| 311 } | |
| 312 FilePath config_symlink_path = FilePath(stateful_container_). | |
| 313 Append("ipsec.conf"); | |
| 314 // Use unlink to remove the symlink directly since file_util::Delete | |
| 315 // cannot delete dangling symlinks. | |
| 316 unlink(config_symlink_path.value().c_str()); | |
| 317 if (file_util::PathExists(config_symlink_path)) { | |
| 318 LOG(ERROR) << "Unable to remove existing file " | |
| 319 << config_symlink_path.value(); | |
| 320 return false; | |
| 321 } | |
| 322 if (symlink(starter_config_path.value().c_str(), | |
| 323 config_symlink_path.value().c_str()) < 0) { | |
| 324 int saved_errno = errno; | |
| 325 LOG(ERROR) << "Unable to symlink config file " | |
| 326 << config_symlink_path.value() << " -> " | |
| 327 << starter_config_path.value() << ": " << saved_errno; | |
| 328 return false; | |
| 329 } | |
| 330 return true; | |
| 331 } | |
| 332 | |
| 333 bool IpsecManager::CreateIpsecRunDirectory() { | |
| 334 if (!file_util::CreateDirectory(FilePath(ipsec_run_path_)) || | |
| 335 !SetIpsecGroup(FilePath(ipsec_run_path_)) || | |
| 336 chmod(ipsec_run_path_.c_str(), kIpsecRunPathMode) != 0) { | |
| 337 LOG(ERROR) << "Unable to create " << ipsec_run_path_; | |
| 338 return false; | |
| 339 } | |
| 340 return true; | |
| 341 } | |
| 342 | |
| 343 bool IpsecManager::Start() { | |
| 344 if (!ipsec_group_) { | |
| 345 struct group group_buffer; | |
| 346 struct group* group_result = NULL; | |
| 347 char buffer[256]; | |
| 348 if (getgrnam_r(kIpsecGroupName, &group_buffer, buffer, | |
| 349 sizeof(buffer), &group_result) != 0 || !group_result) { | |
| 350 LOG(ERROR) << "Cannot find group id for " << kIpsecGroupName; | |
| 351 return false; | |
| 352 } | |
| 353 ipsec_group_ = group_result->gr_gid; | |
| 354 DLOG(INFO) << "Using ipsec group " << ipsec_group_; | |
| 355 } | |
| 356 if (!WriteConfigFiles()) | |
| 357 return false; | |
| 358 if (!CreateIpsecRunDirectory()) | |
| 359 return false; | |
| 360 if (!StartStarter()) | |
| 361 return false; | |
| 362 | |
| 363 start_ticks_ = base::TimeTicks::Now(); | |
| 364 | |
| 365 return true; | |
| 366 } | |
| 367 | |
| 368 int IpsecManager::Poll() { | |
| 369 if (is_running()) return -1; | |
| 370 if (start_ticks_.is_null()) return -1; | |
| 371 if (!file_util::PathExists(FilePath(ipsec_up_file_))) { | |
| 372 if (base::TimeTicks::Now() - start_ticks_ > | |
| 373 base::TimeDelta::FromSeconds(FLAGS_ipsec_timeout)) { | |
| 374 LOG(ERROR) << "IPsec connection timed out"; | |
| 375 OnStopped(false); | |
| 376 // Poll in 1 second in order to check exit conditions. | |
| 377 } | |
| 378 return 1000; | |
| 379 } | |
| 380 | |
| 381 // This indicates that the connection came up successfully. | |
| 382 LOG(INFO) << "IPsec connection now up"; | |
| 383 OnStarted(); | |
| 384 return -1; | |
| 385 } | |
| 386 | |
| 387 void IpsecManager::ProcessOutput() { | |
| 388 ServiceManager::WriteFdToSyslog(output_fd_, ipsec_prefix_, | |
| 389 &partial_output_line_); | |
| 390 } | |
| 391 | |
| 392 bool IpsecManager::IsChild(pid_t pid) { | |
| 393 return pid == starter_->pid(); | |
| 394 } | |
| 395 | |
| 396 void IpsecManager::Stop() { | |
| 397 if (starter_->pid() == 0) { | |
| 398 return; | |
| 399 } | |
| 400 | |
| 401 if (!starter_->Kill(SIGTERM, kTermTimeout)) { | |
| 402 starter_->Kill(SIGKILL, 0); | |
| 403 OnStopped(true); | |
| 404 return; | |
| 405 } | |
| 406 OnStopped(false); | |
| 407 } | |
| OLD | NEW |