OpenSDN source code
metadata_proxy.cc
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2013 Juniper Networks, Inc. All rights reserved.
3  * Copyright Contributors to the OpenSDN project
4  */
5 
6 #include <boost/bind/bind.hpp>
7 #include <boost/asio/ip/host_name.hpp>
8 #include <boost/foreach.hpp>
9 #include <boost/tokenizer.hpp>
10 #include <boost/assign/list_of.hpp>
11 
12 #if defined(RHEL_MAJOR) && (RHEL_MAJOR >= 9)
13 extern "C" {
15 }
16 #else
17 #include <isc/hmacmd5.h>
18 #include <isc/hmacsha.h>
19 #endif
20 
21 #include "base/contrail_ports.h"
22 #include "http/http_request.h"
23 #include "http/http_session.h"
24 #include "http/http_server.h"
26 #include "http/client/http_curl.h"
27 #include "io/event_manager.h"
28 #include "cmn/agent_cmn.h"
29 #include "init/agent_param.h"
30 #include "oper/operdb_init.h"
31 #include "oper/mirror_table.h"
32 #include "oper/interface_common.h"
33 #include "oper/global_vrouter.h"
34 #include "pkt/pkt_handler.h"
35 #include "services/services_types.h"
36 #include "services/services_init.h"
43 
44 using namespace boost::placeholders;
45 
47 
48 #define METADATA_TRACE(obj, arg) \
49 do { \
50  std::ostringstream _str; \
51  _str << arg; \
52  Metadata##obj::TraceMsg(MetadataTraceBuf, __FILE__, __LINE__, _str.str()); \
53 } while (false) \
54 
55 std::map<uint16_t, std::string>
56  g_http_error_map = boost::assign::map_list_of<uint16_t, std::string>
57  (404, "404 Not Found")
58  (500, "500 Internal Server Error")
59  (501, "501 Not Implemented")
60  (502, "502 Bad Gateway")
61  (503, "503 Service Unavailable")
62  (504, "504 Gateway Timeout");
63 
64 static std::string ErrorMessage(uint16_t ec) {
65  std::map<uint16_t, std::string>::iterator iter = g_http_error_map.find(ec);
66  if (iter == g_http_error_map.end())
67  return "";
68  return iter->second;
69 }
70 
71 static std::string GetHmacSha256(const std::string &key, const std::string &data) {
72 #if defined(RHEL_MAJOR) && (RHEL_MAJOR >= 9)
73  const isc_md_type_t *md_type = ISC_MD_SHA256;
74  unsigned char digest[ISC_MAX_MD_SIZE];
75  unsigned int digestlen = sizeof(digest);
76  isc_result_t result = isc_hmac(md_type, (const unsigned char *)key.c_str(), key.length(),
77  (const unsigned char *)data.c_str(), data.length(),
78  digest, &digestlen);
79  if (result != ISC_R_SUCCESS) {
80  return "";
81  }
82 
83  std::stringstream str;
84  for (unsigned int i = 0; i < digestlen; i++) {
85  str << std::hex << std::setfill('0') << std::setw(2) << (int)digest[i];
86  }
87 #else
88  isc_hmacsha256_t hmacsha256;
89  isc_hmacsha256_init(&hmacsha256, (const unsigned char *)key.c_str(),
90  key.length());
91  isc_hmacsha256_update(&hmacsha256, (const isc_uint8_t *)data.c_str(),
92  data.length());
93  unsigned char hmac_sha256_digest[ISC_SHA512_DIGESTLENGTH];
94  isc_hmacsha256_sign(&hmacsha256, hmac_sha256_digest,
96  std::stringstream str;
97  for (unsigned int i = 0; i < ISC_SHA256_DIGESTLENGTH; i++) {
98  str << std::hex << std::setfill('0') << std::setw(2)
99  << (int) hmac_sha256_digest[i];
100  }
101 #endif
102  return str.str();
103 }
104 
106 
108  const std::string &secret)
109  : services_(module), shared_secret_(secret),
110  http_server_(new MetadataServer(services_->agent()->event_manager())),
111  http_server6_(new MetadataServer(services_->agent()->event_manager())),
112  http_client_(new MetadataClient(services_->agent()->event_manager())) {
113 
114  // Register wildcard entry to match any URL coming on the metadata port
116  boost::bind(&MetadataProxy::HandleMetadataRequest, this, _1, _2));
119  services_->agent()->router_id());
120 
121  ipv6_service_address_ = Ip6Address::from_string("::");
123 
125 
126  http_client_->Init();
127 }
128 
130  this->Shutdown();
131 }
132 
134  for (SessionMap::iterator it = metadata_sessions_.begin();
135  it != metadata_sessions_.end(); ) {
136  SessionMap::iterator next = ++it;
137  CloseClientSession(it->second.conn);
138  CloseServerSession(it->first);
139  it = next;
140  }
141 
142  assert(metadata_sessions_.empty());
143  assert(metadata_proxy_sessions_.empty());
144 }
145 
146 void
148  if (http_server_) {
151  http_server_ = NULL;
152  }
153  if (http_server6_) {
157  http_server6_ = NULL;
158  }
159  if (http_client_) {
162  http_client_ = NULL;
163  }
164 }
165 
166 void
168  bool conn_close = false;
169  std::vector<std::string> header_options;
170  std::string vm_ip, vm_uuid, vm_project_uuid;
172  IpAddress ip = session->remote_endpoint().address();
173  if (ip.is_v6()) {
174  // if the address is IPv6 link local (starts with fe80...),
175  // then strip off from the address
176  // an interface name added after '%'
177  if (ip.to_string().find("fe80") == 0) {
178  int i_percent = ip.to_string().find('%');
179  std::string ip_str = ip.to_string().substr(0, i_percent);
180  ip = boost::asio::ip::address::from_string(ip_str);
181  }
182  }
183 
184  if (!services_->agent()->interface_table()->
185  FindVmUuidFromMetadataIp(ip, &vm_ip, &vm_uuid, &vm_project_uuid)) {
186  METADATA_TRACE(Trace, "Error: Interface Config not available; "
187  << "; Request for VM : " << ip);
188  ErrorClose(session, 500);
189  if (ip.is_v6()) {
190  http_server6_->DeleteSession(session);
191  }
192  if (ip.is_v4()) {
193  http_server_->DeleteSession(session);
194  }
195  delete request;
196  return;
197  }
198  std::string signature = GetHmacSha256(shared_secret_, vm_uuid);
199  const HttpRequest::HeaderMap &req_header = request->Headers();
200  for (HttpRequest::HeaderMap::const_iterator it = req_header.begin();
201  it != req_header.end(); ++it) {
202  std::string option = boost::to_lower_copy(it->first);
203  if (option == "host") {
204  continue;
205  }
206  if (option == "connection") {
207  std::string val = boost::to_lower_copy(it->second);
208  if (val == "close")
209  conn_close = true;
210  continue;
211  }
212  header_options.push_back(std::string(it->first + ": " + it->second));
213  }
214 
215  // keystone uses uuids without dashes and that is what ends up in
216  // the nova database entry for the instance. Remove dashes from the
217  // uuid string representation.
218  boost::replace_all(vm_project_uuid, "-", "");
219  header_options.push_back(std::string("X-Forwarded-For: " + vm_ip));
220  header_options.push_back(std::string("X-Instance-ID: " + vm_uuid));
221  header_options.push_back(std::string("X-Tenant-ID: " + vm_project_uuid));
222  header_options.push_back(std::string("X-Instance-ID-Signature: " +
223  signature));
224 
225  std::string uri = request->UrlPath();
226  if (uri.size())
227  uri = uri.substr(1); // ignore the first "/"
228  const std::string &body = request->Body();
229  {
230  std::string nova_hostname;
231  HttpConnection *conn = GetProxyConnection(session, conn_close,
232  &nova_hostname);
233  if (!nova_hostname.empty()) {
234  header_options.insert(header_options.begin(),
235  std::string("Host: " + nova_hostname));
236  }
237 
238  if (conn) {
239  switch(request->GetMethod()) {
240  case HTTP_GET: {
241  conn->HttpGet(uri, true, false, true, header_options,
243  this, conn, HttpSessionPtr(session), _1, _2));
244  METADATA_TRACE(Trace, "GET request for VM : " << vm_ip
245  << " URL : " << uri);
246  break;
247  }
248 
249  case HTTP_HEAD: {
250  conn->HttpHead(uri, true, false, true, header_options,
252  this, conn, HttpSessionPtr(session), _1, _2));
253  METADATA_TRACE(Trace, "HEAD request for VM : " << vm_ip
254  << " URL : " << uri);
255  break;
256  }
257 
258  case HTTP_POST: {
259  conn->HttpPost(body, uri, true, false, true, header_options,
261  this, conn, HttpSessionPtr(session), _1, _2));
262  METADATA_TRACE(Trace, "POST request for VM : " << vm_ip
263  << " URL : " << uri);
264  break;
265  }
266 
267  case HTTP_PUT: {
268  conn->HttpPut(body, uri, true, false, true, header_options,
270  this, conn, HttpSessionPtr(session), _1, _2));
271  METADATA_TRACE(Trace, "PUT request for VM : " << vm_ip
272  << " URL : " << uri);
273  break;
274  }
275 
276  case HTTP_DELETE: {
277  conn->HttpDelete(uri, true, false, true, header_options,
279  this, conn, HttpSessionPtr(session), _1, _2));
280  METADATA_TRACE(Trace, "Delete request for VM : " << vm_ip
281  << " URL : " << uri);
282  break;
283  }
284 
285  default:
286  METADATA_TRACE(Trace, "Error: Unsupported Method; "
287  << "Request Method: " << request->GetMethod()
288  << "; Request for VM: " << vm_ip);
289  CloseClientSession(conn);
290  ErrorClose(session, 501);
291  if (ip.is_v6()) {
292  http_server6_->DeleteSession(session);
293  }
294  if (ip.is_v4()) {
295  http_server_->DeleteSession(session);
296  }
297  break;
298  }
299  } else {
300  METADATA_TRACE(Trace, "Error: Config not available; "
301  << "Request Method: " << request->GetMethod()
302  << "; Request for VM : " << vm_ip);
303  ErrorClose(session, 500);
304  if (ip.is_v6()) {
305  http_server6_->DeleteSession(session);
306  }
307  if (ip.is_v4()) {
308  http_server_->DeleteSession(session);
309  }
310  }
311  }
312 
313  delete request;
314 }
315 
316 // Metadata Response from Nova API service
317 void
319  std::string &msg, boost::system::error_code &ec) {
320  bool delete_session = false;
321  boost::asio::ip::address ip = session->remote_endpoint().address();
322  {
323  // Ignore if session is closed in the meantime
324  SessionMap::iterator it = metadata_sessions_.find(session.get());
325  if (it == metadata_sessions_.end())
326  return;
327 
328  std::string vm_ip, vm_uuid, vm_project_uuid;
329 
330  if (ip.to_string().find("fe80") == 0) {
331  int i_percent = ip.to_string().find('%');
332  std::string ip_str = ip.to_string().substr(0, i_percent);
333  ip = boost::asio::ip::address::from_string(ip_str);
334  }
335 
336  if(!services_->agent()->interface_table()->
337  FindVmUuidFromMetadataIp(ip, &vm_ip, &vm_uuid, &vm_project_uuid)) {
338  LOG(ERROR, "UUID was not found for ip=" << ip.to_string() <<
339  ", in MetadataProxy::HandleMetadataResponse" <<
340  std::endl);
341  return;
342  }
343 
344  if (!ec) {
345  METADATA_TRACE(Trace, "Metadata for VM : " << vm_ip << " Response : " << msg);
346  session->Send(reinterpret_cast<const u_int8_t *>(msg.c_str()),
347  msg.length(), NULL);
348  } else {
349  METADATA_TRACE(Trace, "Metadata for VM : " << vm_ip << " Error : " <<
350  boost::system::system_error(ec).what());
351  CloseClientSession(conn);
352  ErrorClose(session.get(), 502);
353  delete_session = true;
354  goto done;
355  }
356 
358  if (!ec && it->second.close_req) {
359  std::stringstream str(msg);
360  std::string option;
361  str >> option;
362  if (option == "Content-Length:") {
363  str >> it->second.content_len;
364  } else if (msg == "\r\n") {
365  it->second.header_end = true;
366  if (it->second.header_end && !it->second.content_len) {
367  CloseClientSession(it->second.conn);
368  CloseServerSession(session.get());
369  delete_session = true;
370  }
371  } else if (it->second.header_end) {
372  it->second.data_sent += msg.length();
373  if (it->second.data_sent >= it->second.content_len) {
374  CloseClientSession(it->second.conn);
375  CloseServerSession(session.get());
376  delete_session = true;
377  }
378  }
379  }
380  }
381 
382 done:
383  if (delete_session) {
384  if (ip.is_v6()) {
385  http_server6_->DeleteSession(session.get());
386  }
387  if (ip.is_v4()) {
388  http_server_->DeleteSession(session.get());
389  }
390  }
391 }
392 
393 void
395  switch (event) {
396  case TcpSession::CLOSE: {
397  SessionMap::iterator it = metadata_sessions_.find(session);
398  if (it == metadata_sessions_.end())
399  break;
400  CloseClientSession(it->second.conn);
401  metadata_sessions_.erase(it);
402  break;
403  }
404 
405  default:
406  break;
407  }
408 }
409 
410 void
412  boost::asio::ip::address ip = session->remote_endpoint().address();
413  switch (event) {
414  case TcpSession::CLOSE: {
415  {
416  ConnectionSessionMap::iterator it =
417  metadata_proxy_sessions_.find(session->Connection());
418  if (it == metadata_proxy_sessions_.end())
419  break;
420  CloseServerSession(it->second);
421  CloseClientSession(session->Connection());
422  }
423  if (ip.is_v6()) {
424  http_server6_->DeleteSession(session);
425  }
426  if (ip.is_v4()) {
427  http_server_->DeleteSession(session);
428  }
429  break;
430  }
431 
432  default:
433  break;
434  }
435 }
436 
439  std::string *nova_hostname) {
440  SessionMap::iterator it = metadata_sessions_.find(session);
441  if (it != metadata_sessions_.end()) {
442  it->second.close_req = conn_close;
443  return it->second.conn;
444  }
445 
446  uint16_t linklocal_port = session->local_port();
447  IpAddress linklocal_server = session->local_endpoint().address();
448  uint16_t nova_port;
449  Ip4Address nova_server;
450  std::string md_service_name;
451 
452  if (linklocal_server.is_v4() &&
454  GlobalVrouter::kMetadataService, &linklocal_server, &linklocal_port,
455  nova_hostname, &nova_server, &nova_port)) {
456  return NULL;
457  }
458 
459  if (linklocal_server.is_v6() &&
461  GlobalVrouter::kMetadataService6, &linklocal_server, &linklocal_port,
462  nova_hostname, &nova_server, &nova_port)) {
463  return NULL;
464  }
465 
466  HttpConnection *conn = (nova_hostname != 0 && !nova_hostname->empty()) ?
467  http_client_->CreateConnection(*nova_hostname, nova_port) :
468  http_client_->CreateConnection(boost::asio::ip::tcp::endpoint(nova_server, nova_port));
469 
470  map<CURLoption, int> *curl_options = conn->curl_options();
471  curl_options->insert(std::make_pair(CURLOPT_HTTP_TRANSFER_DECODING, 0L));
473  if (conn->use_ssl()) {
474  conn->set_client_cert(
476  conn->set_client_cert_type(
478  conn->set_client_key(
480  conn->set_ca_cert(
482  }
483  conn->RegisterEventCb(
484  boost::bind(&MetadataProxy::OnClientSessionEvent, this, _1, _2));
485  session->RegisterEventCb(
486  boost::bind(&MetadataProxy::OnServerSessionEvent, this, _1, _2));
487  SessionData data(conn, conn_close);
488  metadata_sessions_.insert(SessionPair(session, data));
489  metadata_proxy_sessions_.insert(ConnectionSessionPair(conn, session));
491  return conn;
492 }
493 
494 void
496  session->Close();
497  metadata_sessions_.erase(session);
498 }
499 
500 void
502  HttpClient *client = conn->client();
503  client->RemoveConnection(conn);
504  metadata_proxy_sessions_.erase(conn);
505 }
506 
507 void
508 MetadataProxy::ErrorClose(HttpSession *session, uint16_t error) {
509  std::string message = ErrorMessage(error);
510  char body[512];
511  snprintf(body, sizeof(body), "<html>\n"
512  "<head>\n"
513  " <title>%s</title>\n"
514  "</head>\n"
515  "</html>\n", message.c_str());
516  char response[1024];
517  snprintf(response, sizeof(response),
518  "HTTP/1.1 %s\n"
519  "Content-Type: text/html; charset=UTF-8\n"
520  "Content-Length: %u\n"
521  "\n%s", message.c_str(), (unsigned int) strlen(body), body);
522  session->Send(reinterpret_cast<const u_int8_t *>(response),
523  strlen(response), NULL);
524  CloseServerSession(session);
526 }
boost::asio::ip::address IpAddress
Definition: address.h:14
boost::asio::ip::address_v4 Ip4Address
Definition: address.h:15
std::string metadata_client_cert_type() const
Definition: agent_param.h:249
const bool metadata_use_ssl() const
Definition: agent_param.h:247
std::string metadata_client_key() const
Definition: agent_param.h:252
uint16_t metadata_proxy_port() const
Definition: agent_param.h:242
std::string metadata_ca_cert() const
Definition: agent_param.h:253
std::string metadata_client_cert() const
Definition: agent_param.h:248
InterfaceTable * interface_table() const
Definition: agent.h:468
OperDB * oper_db() const
Definition: agent.cc:1017
void set_metadata_server_port(uint16_t port)
Definition: agent.h:963
AgentParam * params() const
Definition: agent.h:1227
Ip4Address router_id() const
Definition: agent.h:669
static const std::string kMetadataService6
static const std::string kMetadataService
bool FindLinkLocalService(const std::string &service_name, IpAddress *service_ip, uint16_t *service_port, std::string *fabric_hostname, Ip4Address *fabric_ip, uint16_t *fabric_port) const
Get link local service configuration info, for a given service name.
HttpConnection * Connection()
Definition: http_client.h:51
HttpConnection * CreateConnection(boost::asio::ip::tcp::endpoint)
Definition: http_client.cc:455
void RemoveConnection(HttpConnection *)
Definition: http_client.cc:474
void Shutdown()
Definition: http_client.cc:407
void Init()
Definition: http_client.cc:423
int HttpPut(const std::string &put_string, const std::string &path, HttpCb)
Definition: http_client.cc:163
void set_ca_cert(const std::string &ca_cert)
Definition: http_client.h:129
int HttpGet(const std::string &path, HttpCb)
Definition: http_client.cc:136
void set_client_cert(const std::string &client_cert)
Definition: http_client.h:120
void RegisterEventCb(HttpClientSession::SessionEventCb cb)
Definition: http_client.h:117
HttpClient * client()
Definition: http_client.h:101
void set_use_ssl(bool ssl_flag)
Definition: http_client.h:118
int HttpPost(const std::string &post_string, const std::string &path, HttpCb)
Definition: http_client.cc:181
void set_client_cert_type(const std::string &client_cert_type)
Definition: http_client.h:123
void set_client_key(const std::string &client_key)
Definition: http_client.h:126
int HttpHead(const std::string &path, bool header, bool short_timeout, bool reuse, std::vector< std::string > &hdr_options, HttpCb cb)
Definition: http_client.cc:153
int HttpDelete(const std::string &path, HttpCb)
Definition: http_client.cc:197
std::map< CURLoption, int > * curl_options()
Definition: http_client.h:100
const std::string & Body() const
Definition: http_request.h:40
http_method GetMethod() const
Definition: http_request.h:22
std::map< std::string, std::string > HeaderMap
Definition: http_request.h:17
std::string UrlPath() const
Definition: http_request.cc:26
const HeaderMap & Headers() const
Definition: http_request.h:39
void RegisterHandler(const std::string &path, HttpHandlerFn handler)
Definition: http_server.cc:103
void Shutdown()
Definition: http_server.cc:72
void HandleMetadataResponse(HttpConnection *conn, HttpSessionPtr session, std::string &msg, boost::system::error_code &ec)
MetadataClient * http_client_
void CloseClientSession(HttpConnection *conn)
void ErrorClose(HttpSession *sesion, uint16_t error)
std::pair< HttpConnection *, HttpSession * > ConnectionSessionPair
void RegisterListeners()
Registers callbacks to intercept Agent's events emerging when a VRF entry is modified or an Interface...
MetadataProxy(ServicesModule *module, const std::string &secret)
MetadataStats metadata_stats_
std::string shared_secret_
MetadataServer * http_server6_
A pointer to a HTTP server listening on a IPv6 socket for Metadata requests from tenants / virtual ma...
Ip6Address ipv6_service_address_
An IPv6 address on which Metadata6 link local service listens on. We use it instead of IPv4 compute I...
void OnServerSessionEvent(HttpSession *session, TcpSession::Event event)
void HandleMetadataRequest(HttpSession *session, const HttpRequest *request)
void UnregisterListeners()
Unregisters all callbacks that were registered earlier to intercept Agent's events: MetadataProxy::On...
HttpConnection * GetProxyConnection(HttpSession *session, bool conn_close, std::string *nova_hostname)
ServicesModule * services_
virtual ~MetadataProxy()
MetadataServer * http_server_
A pointer to a HTTP server listening on a IPv4 socket for Metadata requests from tenants / virtual ma...
boost::intrusive_ptr< HttpSession > HttpSessionPtr
std::pair< HttpSession *, SessionData > SessionPair
ConnectionSessionMap metadata_proxy_sessions_
SessionMap metadata_sessions_
void OnClientSessionEvent(HttpClientSession *session, TcpSession::Event event)
void CloseServerSession(HttpSession *session)
GlobalVrouter * global_vrouter() const
Definition: operdb_init.h:55
Agent * agent()
Definition: services_init.h:32
static void DeleteServer(TcpServer *server)
Definition: tcp_server.cc:659
virtual bool Initialize(unsigned short port)
Definition: tcp_server.cc:61
virtual void DeleteSession(TcpSession *session)
Definition: tcp_server.cc:200
int GetPort() const
Definition: tcp_server.cc:275
Endpoint remote_endpoint() const
Definition: tcp_session.h:132
Endpoint local_endpoint() const
Definition: tcp_session.cc:207
int32_t local_port() const
Definition: tcp_session.cc:537
virtual bool Send(const uint8_t *data, size_t size, size_t *sent)
Definition: tcp_session.cc:430
void Close()
Definition: tcp_session.cc:356
Definition: trace.h:289
#define HTTP_WILDCARD_ENTRY
Definition: http_server.h:17
#define LOG(_Level, _Msg)
Definition: logging.h:35
#define METADATA_TRACE(obj, arg)
static std::string ErrorMessage(uint16_t ec)
std::map< uint16_t, std::string > g_http_error_map
static std::string GetHmacSha256(const std::string &key, const std::string &data)
#define ISC_SHA512_DIGESTLENGTH
void isc_md_type_t
isc_result_t isc_hmac(const isc_md_type_t *type, const void *key, const size_t keylen, const unsigned char *buf, const size_t len, unsigned char *digest, unsigned int *digestlen)
#define ISC_MAX_MD_SIZE
#define ISC_MD_SHA256
#define ISC_SHA256_DIGESTLENGTH
void RegisterEventCb(SessionEventCb cb)