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