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