1/* 2 * nghttp2 - HTTP/2 C Library 3 * 4 * Copyright (c) 2016 Tatsuhiro Tsujikawa 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining 7 * a copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sublicense, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be 15 * included in all copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 */ 25#include "shrpx_api_downstream_connection.h" 26 27#include <sys/mman.h> 28#include <fcntl.h> 29#include <unistd.h> 30#include <cstdlib> 31 32#include "shrpx_client_handler.h" 33#include "shrpx_upstream.h" 34#include "shrpx_downstream.h" 35#include "shrpx_worker.h" 36#include "shrpx_connection_handler.h" 37#include "shrpx_log.h" 38 39namespace shrpx { 40 41namespace { 42// List of API endpoints 43const std::array<APIEndpoint, 2> &apis() { 44 static const auto apis = new std::array<APIEndpoint, 2>{ 45 APIEndpoint{ 46 StringRef::from_lit("/api/v1beta1/backendconfig"), 47 true, 48 (1 << API_METHOD_POST) | (1 << API_METHOD_PUT), 49 &APIDownstreamConnection::handle_backendconfig, 50 }, 51 APIEndpoint{ 52 StringRef::from_lit("/api/v1beta1/configrevision"), 53 true, 54 (1 << API_METHOD_GET), 55 &APIDownstreamConnection::handle_configrevision, 56 }, 57 }; 58 59 return *apis; 60} 61} // namespace 62 63namespace { 64// The method string. This must be same order of APIMethod. 65constexpr StringRef API_METHOD_STRING[] = { 66 StringRef::from_lit("GET"), 67 StringRef::from_lit("POST"), 68 StringRef::from_lit("PUT"), 69}; 70} // namespace 71 72APIDownstreamConnection::APIDownstreamConnection(Worker *worker) 73 : worker_(worker), api_(nullptr), fd_(-1), shutdown_read_(false) {} 74 75APIDownstreamConnection::~APIDownstreamConnection() { 76 if (fd_ != -1) { 77 close(fd_); 78 } 79} 80 81int APIDownstreamConnection::attach_downstream(Downstream *downstream) { 82 if (LOG_ENABLED(INFO)) { 83 DCLOG(INFO, this) << "Attaching to DOWNSTREAM:" << downstream; 84 } 85 86 downstream_ = downstream; 87 88 return 0; 89} 90 91void APIDownstreamConnection::detach_downstream(Downstream *downstream) { 92 if (LOG_ENABLED(INFO)) { 93 DCLOG(INFO, this) << "Detaching from DOWNSTREAM:" << downstream; 94 } 95 downstream_ = nullptr; 96} 97 98int APIDownstreamConnection::send_reply(unsigned int http_status, 99 APIStatusCode api_status, 100 const StringRef &data) { 101 shutdown_read_ = true; 102 103 auto upstream = downstream_->get_upstream(); 104 105 auto &resp = downstream_->response(); 106 107 resp.http_status = http_status; 108 109 auto &balloc = downstream_->get_block_allocator(); 110 111 StringRef api_status_str; 112 113 switch (api_status) { 114 case APIStatusCode::SUCCESS: 115 api_status_str = StringRef::from_lit("Success"); 116 break; 117 case APIStatusCode::FAILURE: 118 api_status_str = StringRef::from_lit("Failure"); 119 break; 120 default: 121 assert(0); 122 } 123 124 constexpr auto M1 = StringRef::from_lit("{\"status\":\""); 125 constexpr auto M2 = StringRef::from_lit("\",\"code\":"); 126 constexpr auto M3 = StringRef::from_lit("}"); 127 128 // 3 is the number of digits in http_status, assuming it is 3 digits 129 // number. 130 auto buflen = M1.size() + M2.size() + M3.size() + data.size() + 131 api_status_str.size() + 3; 132 133 auto buf = make_byte_ref(balloc, buflen); 134 auto p = buf.base; 135 136 p = std::copy(std::begin(M1), std::end(M1), p); 137 p = std::copy(std::begin(api_status_str), std::end(api_status_str), p); 138 p = std::copy(std::begin(M2), std::end(M2), p); 139 p = util::utos(p, http_status); 140 p = std::copy(std::begin(data), std::end(data), p); 141 p = std::copy(std::begin(M3), std::end(M3), p); 142 143 buf.len = p - buf.base; 144 145 auto content_length = util::make_string_ref_uint(balloc, buf.len); 146 147 resp.fs.add_header_token(StringRef::from_lit("content-length"), 148 content_length, false, http2::HD_CONTENT_LENGTH); 149 150 switch (http_status) { 151 case 400: 152 case 405: 153 case 413: 154 resp.fs.add_header_token(StringRef::from_lit("connection"), 155 StringRef::from_lit("close"), false, 156 http2::HD_CONNECTION); 157 break; 158 } 159 160 if (upstream->send_reply(downstream_, buf.base, buf.len) != 0) { 161 return -1; 162 } 163 164 return 0; 165} 166 167namespace { 168const APIEndpoint *lookup_api(const StringRef &path) { 169 switch (path.size()) { 170 case 26: 171 switch (path[25]) { 172 case 'g': 173 if (util::streq_l("/api/v1beta1/backendconfi", std::begin(path), 25)) { 174 return &apis()[0]; 175 } 176 break; 177 } 178 break; 179 case 27: 180 switch (path[26]) { 181 case 'n': 182 if (util::streq_l("/api/v1beta1/configrevisio", std::begin(path), 26)) { 183 return &apis()[1]; 184 } 185 break; 186 } 187 break; 188 } 189 return nullptr; 190} 191} // namespace 192 193int APIDownstreamConnection::push_request_headers() { 194 auto &req = downstream_->request(); 195 196 auto path = 197 StringRef{std::begin(req.path), 198 std::find(std::begin(req.path), std::end(req.path), '?')}; 199 200 api_ = lookup_api(path); 201 202 if (!api_) { 203 send_reply(404, APIStatusCode::FAILURE); 204 205 return 0; 206 } 207 208 switch (req.method) { 209 case HTTP_GET: 210 if (!(api_->allowed_methods & (1 << API_METHOD_GET))) { 211 error_method_not_allowed(); 212 return 0; 213 } 214 break; 215 case HTTP_POST: 216 if (!(api_->allowed_methods & (1 << API_METHOD_POST))) { 217 error_method_not_allowed(); 218 return 0; 219 } 220 break; 221 case HTTP_PUT: 222 if (!(api_->allowed_methods & (1 << API_METHOD_PUT))) { 223 error_method_not_allowed(); 224 return 0; 225 } 226 break; 227 default: 228 error_method_not_allowed(); 229 return 0; 230 } 231 232 // This works with req.fs.content_length == -1 233 if (req.fs.content_length > 234 static_cast<int64_t>(get_config()->api.max_request_body)) { 235 send_reply(413, APIStatusCode::FAILURE); 236 237 return 0; 238 } 239 240 switch (req.method) { 241 case HTTP_POST: 242 case HTTP_PUT: { 243 char tempname[] = "/tmp/nghttpx-api.XXXXXX"; 244#ifdef HAVE_MKOSTEMP 245 fd_ = mkostemp(tempname, O_CLOEXEC); 246#else // !HAVE_MKOSTEMP 247 fd_ = mkstemp(tempname); 248#endif // !HAVE_MKOSTEMP 249 if (fd_ == -1) { 250 send_reply(500, APIStatusCode::FAILURE); 251 252 return 0; 253 } 254#ifndef HAVE_MKOSTEMP 255 util::make_socket_closeonexec(fd_); 256#endif // HAVE_MKOSTEMP 257 unlink(tempname); 258 break; 259 } 260 } 261 262 downstream_->set_request_header_sent(true); 263 auto src = downstream_->get_blocked_request_buf(); 264 auto dest = downstream_->get_request_buf(); 265 src->remove(*dest); 266 267 return 0; 268} 269 270int APIDownstreamConnection::error_method_not_allowed() { 271 auto &resp = downstream_->response(); 272 273 size_t len = 0; 274 for (uint8_t i = 0; i < API_METHOD_MAX; ++i) { 275 if (api_->allowed_methods & (1 << i)) { 276 // The length of method + ", " 277 len += API_METHOD_STRING[i].size() + 2; 278 } 279 } 280 281 assert(len > 0); 282 283 auto &balloc = downstream_->get_block_allocator(); 284 285 auto iov = make_byte_ref(balloc, len + 1); 286 auto p = iov.base; 287 for (uint8_t i = 0; i < API_METHOD_MAX; ++i) { 288 if (api_->allowed_methods & (1 << i)) { 289 auto &s = API_METHOD_STRING[i]; 290 p = std::copy(std::begin(s), std::end(s), p); 291 p = std::copy_n(", ", 2, p); 292 } 293 } 294 295 p -= 2; 296 *p = '\0'; 297 298 resp.fs.add_header_token(StringRef::from_lit("allow"), StringRef{iov.base, p}, 299 false, -1); 300 return send_reply(405, APIStatusCode::FAILURE); 301} 302 303int APIDownstreamConnection::push_upload_data_chunk(const uint8_t *data, 304 size_t datalen) { 305 if (shutdown_read_ || !api_->require_body) { 306 return 0; 307 } 308 309 auto &req = downstream_->request(); 310 auto &apiconf = get_config()->api; 311 312 if (static_cast<size_t>(req.recv_body_length) > apiconf.max_request_body) { 313 send_reply(413, APIStatusCode::FAILURE); 314 315 return 0; 316 } 317 318 ssize_t nwrite; 319 while ((nwrite = write(fd_, data, datalen)) == -1 && errno == EINTR) 320 ; 321 if (nwrite == -1) { 322 auto error = errno; 323 LOG(ERROR) << "Could not write API request body: errno=" << error; 324 send_reply(500, APIStatusCode::FAILURE); 325 326 return 0; 327 } 328 329 // We don't have to call Upstream::resume_read() here, because 330 // request buffer is effectively unlimited. Actually, we cannot 331 // call it here since it could recursively call this function again. 332 333 return 0; 334} 335 336int APIDownstreamConnection::end_upload_data() { 337 if (shutdown_read_) { 338 return 0; 339 } 340 341 return api_->handler(*this); 342} 343 344int APIDownstreamConnection::handle_backendconfig() { 345 auto &req = downstream_->request(); 346 347 if (req.recv_body_length == 0) { 348 send_reply(200, APIStatusCode::SUCCESS); 349 350 return 0; 351 } 352 353 auto rp = mmap(nullptr, req.recv_body_length, PROT_READ, MAP_SHARED, fd_, 0); 354 if (rp == reinterpret_cast<void *>(-1)) { 355 send_reply(500, APIStatusCode::FAILURE); 356 return 0; 357 } 358 359 auto unmapper = defer(munmap, rp, req.recv_body_length); 360 361 Config new_config{}; 362 new_config.conn.downstream = std::make_shared<DownstreamConfig>(); 363 const auto &downstreamconf = new_config.conn.downstream; 364 365 auto config = get_config(); 366 auto &src = config->conn.downstream; 367 368 downstreamconf->timeout = src->timeout; 369 downstreamconf->connections_per_host = src->connections_per_host; 370 downstreamconf->connections_per_frontend = src->connections_per_frontend; 371 downstreamconf->request_buffer_size = src->request_buffer_size; 372 downstreamconf->response_buffer_size = src->response_buffer_size; 373 downstreamconf->family = src->family; 374 375 std::set<StringRef> include_set; 376 std::map<StringRef, size_t> pattern_addr_indexer; 377 378 for (auto first = reinterpret_cast<const uint8_t *>(rp), 379 last = first + req.recv_body_length; 380 first != last;) { 381 auto eol = std::find(first, last, '\n'); 382 if (eol == last) { 383 break; 384 } 385 386 if (first == eol || *first == '#') { 387 first = ++eol; 388 continue; 389 } 390 391 auto eq = std::find(first, eol, '='); 392 if (eq == eol) { 393 send_reply(400, APIStatusCode::FAILURE); 394 return 0; 395 } 396 397 auto opt = StringRef{first, eq}; 398 auto optval = StringRef{eq + 1, eol}; 399 400 auto optid = option_lookup_token(opt.c_str(), opt.size()); 401 402 switch (optid) { 403 case SHRPX_OPTID_BACKEND: 404 break; 405 default: 406 first = ++eol; 407 continue; 408 } 409 410 if (parse_config(&new_config, optid, opt, optval, include_set, 411 pattern_addr_indexer) != 0) { 412 send_reply(400, APIStatusCode::FAILURE); 413 return 0; 414 } 415 416 first = ++eol; 417 } 418 419 auto &tlsconf = config->tls; 420 if (configure_downstream_group(&new_config, config->http2_proxy, true, 421 tlsconf) != 0) { 422 send_reply(400, APIStatusCode::FAILURE); 423 return 0; 424 } 425 426 auto conn_handler = worker_->get_connection_handler(); 427 428 conn_handler->send_replace_downstream(downstreamconf); 429 430 send_reply(200, APIStatusCode::SUCCESS); 431 432 return 0; 433} 434 435int APIDownstreamConnection::handle_configrevision() { 436 auto config = get_config(); 437 auto &balloc = downstream_->get_block_allocator(); 438 439 // Construct the following string: 440 // , 441 // "data":{ 442 // "configRevision": N 443 // } 444 auto data = concat_string_ref( 445 balloc, StringRef::from_lit(R"(,"data":{"configRevision":)"), 446 util::make_string_ref_uint(balloc, config->config_revision), 447 StringRef::from_lit("}")); 448 449 send_reply(200, APIStatusCode::SUCCESS, data); 450 451 return 0; 452} 453 454void APIDownstreamConnection::pause_read(IOCtrlReason reason) {} 455 456int APIDownstreamConnection::resume_read(IOCtrlReason reason, size_t consumed) { 457 return 0; 458} 459 460void APIDownstreamConnection::force_resume_read() {} 461 462int APIDownstreamConnection::on_read() { return 0; } 463 464int APIDownstreamConnection::on_write() { return 0; } 465 466void APIDownstreamConnection::on_upstream_change(Upstream *upstream) {} 467 468bool APIDownstreamConnection::poolable() const { return false; } 469 470const std::shared_ptr<DownstreamAddrGroup> & 471APIDownstreamConnection::get_downstream_addr_group() const { 472 static std::shared_ptr<DownstreamAddrGroup> s; 473 return s; 474} 475 476DownstreamAddr *APIDownstreamConnection::get_addr() const { return nullptr; } 477 478} // namespace shrpx 479