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