// Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "node_dtrace.h" #ifdef HAVE_DTRACE #include "node_provider.h" #elif HAVE_ETW #include "node_win32_etw_provider-inl.h" #else #define NODE_HTTP_SERVER_REQUEST(arg0, arg1) #define NODE_HTTP_SERVER_REQUEST_ENABLED() (0) #define NODE_HTTP_SERVER_RESPONSE(arg0) #define NODE_HTTP_SERVER_RESPONSE_ENABLED() (0) #define NODE_HTTP_CLIENT_REQUEST(arg0, arg1) #define NODE_HTTP_CLIENT_REQUEST_ENABLED() (0) #define NODE_HTTP_CLIENT_RESPONSE(arg0) #define NODE_HTTP_CLIENT_RESPONSE_ENABLED() (0) #define NODE_NET_SERVER_CONNECTION(arg0) #define NODE_NET_SERVER_CONNECTION_ENABLED() (0) #define NODE_NET_STREAM_END(arg0) #define NODE_NET_STREAM_END_ENABLED() (0) #define NODE_GC_START(arg0, arg1, arg2) #define NODE_GC_DONE(arg0, arg1, arg2) #endif #include "env-inl.h" #include "node_errors.h" #include namespace node { using v8::Context; using v8::FunctionCallbackInfo; using v8::GCCallbackFlags; using v8::GCType; using v8::HandleScope; using v8::Isolate; using v8::Local; using v8::Object; using v8::String; using v8::Value; #define SLURP_STRING(obj, member, valp) \ if (!(obj)->IsObject()) { \ return node::THROW_ERR_INVALID_ARG_TYPE(env, \ "expected object for " #obj " to contain string member " #member); \ } \ node::Utf8Value _##member(env->isolate(), \ obj->Get(env->context(), \ OneByteString(env->isolate(), #member)).ToLocalChecked()); \ if ((*(const char **)valp = *_##member) == nullptr) \ *(const char **)valp = ""; #define SLURP_INT(obj, member, valp) \ if (!(obj)->IsObject()) { \ return node::THROW_ERR_INVALID_ARG_TYPE( \ env, \ "expected object for " #obj " to contain integer member " #member); \ } \ *valp = obj->Get(env->context(), \ OneByteString(env->isolate(), #member)).ToLocalChecked() \ ->Int32Value(env->context()) \ .FromJust(); #define SLURP_OBJECT(obj, member, valp) \ if (!(obj)->IsObject()) { \ return node::THROW_ERR_INVALID_ARG_TYPE(env, \ "expected object for " #obj " to contain object member " #member); \ } \ *valp = Local::Cast(obj->Get(env->context(), \ OneByteString(env->isolate(), #member)).ToLocalChecked()); #define SLURP_CONNECTION(arg, conn) \ if (!(arg)->IsObject()) { \ return node::THROW_ERR_INVALID_ARG_TYPE(env, \ "expected argument " #arg " to be a connection object"); \ } \ node_dtrace_connection_t conn; \ Local _##conn = Local::Cast(arg); \ Local _handle = \ (_##conn)->Get(env->context(), \ FIXED_ONE_BYTE_STRING(env->isolate(), "_handle")) \ .ToLocalChecked(); \ if (_handle->IsObject()) { \ SLURP_INT(_handle.As(), fd, &conn.fd); \ } else { \ conn.fd = -1; \ } \ SLURP_STRING(_##conn, remoteAddress, &conn.remote); \ SLURP_INT(_##conn, remotePort, &conn.port); \ SLURP_INT(_##conn, bufferSize, &conn.buffered); #define SLURP_CONNECTION_HTTP_CLIENT(arg, conn) \ if (!(arg)->IsObject()) { \ return node::THROW_ERR_INVALID_ARG_TYPE(env, \ "expected argument " #arg " to be a connection object"); \ } \ node_dtrace_connection_t conn; \ Local _##conn = Local::Cast(arg); \ SLURP_INT(_##conn, fd, &conn.fd); \ SLURP_STRING(_##conn, host, &conn.remote); \ SLURP_INT(_##conn, port, &conn.port); \ SLURP_INT(_##conn, bufferSize, &conn.buffered); #define SLURP_CONNECTION_HTTP_CLIENT_RESPONSE(arg0, arg1, conn) \ if (!(arg0)->IsObject()) { \ return node::THROW_ERR_INVALID_ARG_TYPE(env, \ "expected argument " #arg0 " to be a connection object"); \ } \ if (!(arg1)->IsObject()) { \ return node::THROW_ERR_INVALID_ARG_TYPE(env, \ "expected argument " #arg1 " to be a connection object"); \ } \ node_dtrace_connection_t conn; \ Local _##conn = Local::Cast(arg0); \ SLURP_INT(_##conn, fd, &conn.fd); \ SLURP_INT(_##conn, bufferSize, &conn.buffered); \ _##conn = Local::Cast(arg1); \ SLURP_STRING(_##conn, host, &conn.remote); \ SLURP_INT(_##conn, port, &conn.port); void DTRACE_NET_SERVER_CONNECTION(const FunctionCallbackInfo& args) { if (!NODE_NET_SERVER_CONNECTION_ENABLED()) return; Environment* env = Environment::GetCurrent(args); SLURP_CONNECTION(args[0], conn); NODE_NET_SERVER_CONNECTION(&conn, conn.remote, conn.port, conn.fd); } void DTRACE_NET_STREAM_END(const FunctionCallbackInfo& args) { if (!NODE_NET_STREAM_END_ENABLED()) return; Environment* env = Environment::GetCurrent(args); SLURP_CONNECTION(args[0], conn); NODE_NET_STREAM_END(&conn, conn.remote, conn.port, conn.fd); } void DTRACE_HTTP_SERVER_REQUEST(const FunctionCallbackInfo& args) { node_dtrace_http_server_request_t req; if (!NODE_HTTP_SERVER_REQUEST_ENABLED()) return; Environment* env = Environment::GetCurrent(args); HandleScope scope(env->isolate()); Local arg0 = Local::Cast(args[0]); Local headers; memset(&req, 0, sizeof(req)); req._un.version = 1; SLURP_STRING(arg0, url, &req.url); SLURP_STRING(arg0, method, &req.method); SLURP_OBJECT(arg0, headers, &headers); if (!(headers)->IsObject()) { return node::THROW_ERR_INVALID_ARG_TYPE(env, "expected object for request to contain string member headers"); } Local strfwdfor = headers->Get( env->context(), env->x_forwarded_string()).ToLocalChecked(); node::Utf8Value fwdfor(env->isolate(), strfwdfor); if (!strfwdfor->IsString() || (req.forwardedFor = *fwdfor) == nullptr) req.forwardedFor = const_cast(""); SLURP_CONNECTION(args[1], conn); NODE_HTTP_SERVER_REQUEST(&req, &conn, conn.remote, conn.port, req.method, \ req.url, conn.fd); } void DTRACE_HTTP_SERVER_RESPONSE(const FunctionCallbackInfo& args) { if (!NODE_HTTP_SERVER_RESPONSE_ENABLED()) return; Environment* env = Environment::GetCurrent(args); SLURP_CONNECTION(args[0], conn); NODE_HTTP_SERVER_RESPONSE(&conn, conn.remote, conn.port, conn.fd); } void DTRACE_HTTP_CLIENT_REQUEST(const FunctionCallbackInfo& args) { node_dtrace_http_client_request_t req; char* header; if (!NODE_HTTP_CLIENT_REQUEST_ENABLED()) return; Environment* env = Environment::GetCurrent(args); HandleScope scope(env->isolate()); /* * For the method and URL, we're going to dig them out of the header. This * is not as efficient as it could be, but we would rather not force the * caller here to retain their method and URL until the time at which * DTRACE_HTTP_CLIENT_REQUEST can be called. */ Local arg0 = Local::Cast(args[0]); SLURP_STRING(arg0, _header, &header); req.method = header; while (*header != '\0' && *header != ' ') header++; if (*header != '\0') *header++ = '\0'; req.url = header; while (*header != '\0' && *header != ' ') header++; *header = '\0'; SLURP_CONNECTION_HTTP_CLIENT(args[1], conn); NODE_HTTP_CLIENT_REQUEST(&req, &conn, conn.remote, conn.port, req.method, \ req.url, conn.fd); } void DTRACE_HTTP_CLIENT_RESPONSE(const FunctionCallbackInfo& args) { if (!NODE_HTTP_CLIENT_RESPONSE_ENABLED()) return; Environment* env = Environment::GetCurrent(args); SLURP_CONNECTION_HTTP_CLIENT_RESPONSE(args[0], args[1], conn); NODE_HTTP_CLIENT_RESPONSE(&conn, conn.remote, conn.port, conn.fd); } void dtrace_gc_start(Isolate* isolate, GCType type, GCCallbackFlags flags, void* data) { // Previous versions of this probe point only logged type and flags. // That's why for reasons of backwards compatibility the isolate goes last. NODE_GC_START(type, flags, isolate); } void dtrace_gc_done(Isolate* isolate, GCType type, GCCallbackFlags flags, void* data) { // Previous versions of this probe point only logged type and flags. // That's why for reasons of backwards compatibility the isolate goes last. NODE_GC_DONE(type, flags, isolate); } void InitDTrace(Environment* env) { #ifdef HAVE_ETW // ETW is neither thread-safe nor does it clean up resources on exit, // so we can use it only on the main thread. if (env->is_main_thread()) { init_etw(); } #endif // We need to use the variant of GC callbacks that takes data to // avoid running into DCHECKs when multiple Environments try to add // the same callback to the same isolate multiple times. env->isolate()->AddGCPrologueCallback(dtrace_gc_start, env); env->isolate()->AddGCEpilogueCallback(dtrace_gc_done, env); env->AddCleanupHook([](void* data) { Environment* env = static_cast(data); env->isolate()->RemoveGCPrologueCallback(dtrace_gc_start, env); env->isolate()->RemoveGCEpilogueCallback(dtrace_gc_done, env); }, env); } void InitializeDTrace(Local target, Local unused, Local context, void* priv) { Environment* env = Environment::GetCurrent(context); #if defined HAVE_DTRACE || defined HAVE_ETW # define NODE_PROBE(name) env->SetMethod(target, #name, name); NODE_PROBE(DTRACE_NET_SERVER_CONNECTION) NODE_PROBE(DTRACE_NET_STREAM_END) NODE_PROBE(DTRACE_HTTP_SERVER_REQUEST) NODE_PROBE(DTRACE_HTTP_SERVER_RESPONSE) NODE_PROBE(DTRACE_HTTP_CLIENT_REQUEST) NODE_PROBE(DTRACE_HTTP_CLIENT_RESPONSE) # undef NODE_PROBE #endif } } // namespace node NODE_MODULE_CONTEXT_AWARE_INTERNAL(dtrace, node::InitializeDTrace)