1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 #include "node_dtrace.h"
23
24 #ifdef HAVE_DTRACE
25 #include "node_provider.h"
26 #elif HAVE_ETW
27 #include "node_win32_etw_provider-inl.h"
28 #else
29 #define NODE_HTTP_SERVER_REQUEST(arg0, arg1)
30 #define NODE_HTTP_SERVER_REQUEST_ENABLED() (0)
31 #define NODE_HTTP_SERVER_RESPONSE(arg0)
32 #define NODE_HTTP_SERVER_RESPONSE_ENABLED() (0)
33 #define NODE_HTTP_CLIENT_REQUEST(arg0, arg1)
34 #define NODE_HTTP_CLIENT_REQUEST_ENABLED() (0)
35 #define NODE_HTTP_CLIENT_RESPONSE(arg0)
36 #define NODE_HTTP_CLIENT_RESPONSE_ENABLED() (0)
37 #define NODE_NET_SERVER_CONNECTION(arg0)
38 #define NODE_NET_SERVER_CONNECTION_ENABLED() (0)
39 #define NODE_NET_STREAM_END(arg0)
40 #define NODE_NET_STREAM_END_ENABLED() (0)
41 #define NODE_GC_START(arg0, arg1, arg2)
42 #define NODE_GC_DONE(arg0, arg1, arg2)
43 #endif
44
45 #include "env-inl.h"
46 #include "node_errors.h"
47 #include "node_external_reference.h"
48
49 #include <cstring>
50
51 namespace node {
52
53 using v8::Context;
54 using v8::FunctionCallbackInfo;
55 using v8::GCCallbackFlags;
56 using v8::GCType;
57 using v8::HandleScope;
58 using v8::Isolate;
59 using v8::Local;
60 using v8::Object;
61 using v8::Value;
62
63 #define SLURP_STRING(obj, member, valp) \
64 if (!(obj)->IsObject()) { \
65 return node::THROW_ERR_INVALID_ARG_TYPE(env, \
66 "expected object for " #obj " to contain string member " #member); \
67 } \
68 node::Utf8Value _##member(env->isolate(), \
69 obj->Get(env->context(), \
70 OneByteString(env->isolate(), #member)).ToLocalChecked()); \
71 if ((*(const char **)valp = *_##member) == nullptr) \
72 *(const char **)valp = "<unknown>";
73
74 #define SLURP_INT(obj, member, valp) \
75 if (!(obj)->IsObject()) { \
76 return node::THROW_ERR_INVALID_ARG_TYPE( \
77 env, \
78 "expected object for " #obj " to contain integer member " #member); \
79 } \
80 *valp = obj->Get(env->context(), \
81 OneByteString(env->isolate(), #member)).ToLocalChecked() \
82 ->Int32Value(env->context()) \
83 .FromJust();
84
85 #define SLURP_OBJECT(obj, member, valp) \
86 if (!(obj)->IsObject()) { \
87 return node::THROW_ERR_INVALID_ARG_TYPE(env, \
88 "expected object for " #obj " to contain object member " #member); \
89 } \
90 *valp = obj->Get(env->context(), \
91 OneByteString(env->isolate(), #member)).ToLocalChecked().As<Object>();
92
93 #define SLURP_CONNECTION(arg, conn) \
94 if (!(arg)->IsObject()) { \
95 return node::THROW_ERR_INVALID_ARG_TYPE(env, \
96 "expected argument " #arg " to be a connection object"); \
97 } \
98 node_dtrace_connection_t conn; \
99 Local<Object> _##conn = arg.As<Object>(); \
100 Local<Value> _handle = \
101 (_##conn)->Get(env->context(), \
102 FIXED_ONE_BYTE_STRING(env->isolate(), "_handle")) \
103 .ToLocalChecked(); \
104 if (_handle->IsObject()) { \
105 SLURP_INT(_handle.As<Object>(), fd, &conn.fd); \
106 } else { \
107 conn.fd = -1; \
108 } \
109 SLURP_STRING(_##conn, remoteAddress, &conn.remote); \
110 SLURP_INT(_##conn, remotePort, &conn.port); \
111 SLURP_INT(_##conn, bufferSize, &conn.buffered);
112
113 #define SLURP_CONNECTION_HTTP_CLIENT(arg, conn) \
114 if (!(arg)->IsObject()) { \
115 return node::THROW_ERR_INVALID_ARG_TYPE(env, \
116 "expected argument " #arg " to be a connection object"); \
117 } \
118 node_dtrace_connection_t conn; \
119 Local<Object> _##conn = arg.As<Object>(); \
120 SLURP_INT(_##conn, fd, &conn.fd); \
121 SLURP_STRING(_##conn, host, &conn.remote); \
122 SLURP_INT(_##conn, port, &conn.port); \
123 SLURP_INT(_##conn, bufferSize, &conn.buffered);
124
125 #define SLURP_CONNECTION_HTTP_CLIENT_RESPONSE(arg0, arg1, conn) \
126 if (!(arg0)->IsObject()) { \
127 return node::THROW_ERR_INVALID_ARG_TYPE(env, \
128 "expected argument " #arg0 " to be a connection object"); \
129 } \
130 if (!(arg1)->IsObject()) { \
131 return node::THROW_ERR_INVALID_ARG_TYPE(env, \
132 "expected argument " #arg1 " to be a connection object"); \
133 } \
134 node_dtrace_connection_t conn; \
135 Local<Object> _##conn = arg0.As<Object>(); \
136 SLURP_INT(_##conn, fd, &conn.fd); \
137 SLURP_INT(_##conn, bufferSize, &conn.buffered); \
138 _##conn = arg1.As<Object>(); \
139 SLURP_STRING(_##conn, host, &conn.remote); \
140 SLURP_INT(_##conn, port, &conn.port);
141
142
DTRACE_NET_SERVER_CONNECTION(const FunctionCallbackInfo<Value> & args)143 void DTRACE_NET_SERVER_CONNECTION(const FunctionCallbackInfo<Value>& args) {
144 if (!NODE_NET_SERVER_CONNECTION_ENABLED())
145 return;
146 Environment* env = Environment::GetCurrent(args);
147 SLURP_CONNECTION(args[0], conn);
148 NODE_NET_SERVER_CONNECTION(&conn, conn.remote, conn.port, conn.fd);
149 }
150
151
DTRACE_NET_STREAM_END(const FunctionCallbackInfo<Value> & args)152 void DTRACE_NET_STREAM_END(const FunctionCallbackInfo<Value>& args) {
153 if (!NODE_NET_STREAM_END_ENABLED())
154 return;
155 Environment* env = Environment::GetCurrent(args);
156 SLURP_CONNECTION(args[0], conn);
157 NODE_NET_STREAM_END(&conn, conn.remote, conn.port, conn.fd);
158 }
159
DTRACE_HTTP_SERVER_REQUEST(const FunctionCallbackInfo<Value> & args)160 void DTRACE_HTTP_SERVER_REQUEST(const FunctionCallbackInfo<Value>& args) {
161 node_dtrace_http_server_request_t req;
162
163 if (!NODE_HTTP_SERVER_REQUEST_ENABLED())
164 return;
165
166 Environment* env = Environment::GetCurrent(args);
167 HandleScope scope(env->isolate());
168 Local<Object> arg0 = args[0].As<Object>();
169 Local<Object> headers;
170
171 memset(&req, 0, sizeof(req));
172 req._un.version = 1;
173 SLURP_STRING(arg0, url, &req.url);
174 SLURP_STRING(arg0, method, &req.method);
175 SLURP_OBJECT(arg0, headers, &headers);
176
177 if (!(headers)->IsObject()) {
178 return node::THROW_ERR_INVALID_ARG_TYPE(env,
179 "expected object for request to contain string member headers");
180 }
181
182 Local<Value> strfwdfor = headers->Get(
183 env->context(), env->x_forwarded_string()).ToLocalChecked();
184 node::Utf8Value fwdfor(env->isolate(), strfwdfor);
185
186 if (!strfwdfor->IsString() || (req.forwardedFor = *fwdfor) == nullptr)
187 req.forwardedFor = const_cast<char*>("");
188
189 SLURP_CONNECTION(args[1], conn);
190 NODE_HTTP_SERVER_REQUEST(&req, &conn, conn.remote, conn.port, req.method, \
191 req.url, conn.fd);
192 }
193
194
DTRACE_HTTP_SERVER_RESPONSE(const FunctionCallbackInfo<Value> & args)195 void DTRACE_HTTP_SERVER_RESPONSE(const FunctionCallbackInfo<Value>& args) {
196 if (!NODE_HTTP_SERVER_RESPONSE_ENABLED())
197 return;
198 Environment* env = Environment::GetCurrent(args);
199 SLURP_CONNECTION(args[0], conn);
200 NODE_HTTP_SERVER_RESPONSE(&conn, conn.remote, conn.port, conn.fd);
201 }
202
203
DTRACE_HTTP_CLIENT_REQUEST(const FunctionCallbackInfo<Value> & args)204 void DTRACE_HTTP_CLIENT_REQUEST(const FunctionCallbackInfo<Value>& args) {
205 node_dtrace_http_client_request_t req;
206 char* header;
207
208 if (!NODE_HTTP_CLIENT_REQUEST_ENABLED())
209 return;
210
211 Environment* env = Environment::GetCurrent(args);
212 HandleScope scope(env->isolate());
213
214 /*
215 * For the method and URL, we're going to dig them out of the header. This
216 * is not as efficient as it could be, but we would rather not force the
217 * caller here to retain their method and URL until the time at which
218 * DTRACE_HTTP_CLIENT_REQUEST can be called.
219 */
220 Local<Object> arg0 = args[0].As<Object>();
221 SLURP_STRING(arg0, _header, &header);
222
223 req.method = header;
224
225 while (*header != '\0' && *header != ' ')
226 header++;
227
228 if (*header != '\0')
229 *header++ = '\0';
230
231 req.url = header;
232
233 while (*header != '\0' && *header != ' ')
234 header++;
235
236 *header = '\0';
237
238 SLURP_CONNECTION_HTTP_CLIENT(args[1], conn);
239 NODE_HTTP_CLIENT_REQUEST(&req, &conn, conn.remote, conn.port, req.method, \
240 req.url, conn.fd);
241 }
242
243
DTRACE_HTTP_CLIENT_RESPONSE(const FunctionCallbackInfo<Value> & args)244 void DTRACE_HTTP_CLIENT_RESPONSE(const FunctionCallbackInfo<Value>& args) {
245 if (!NODE_HTTP_CLIENT_RESPONSE_ENABLED())
246 return;
247 Environment* env = Environment::GetCurrent(args);
248 SLURP_CONNECTION_HTTP_CLIENT_RESPONSE(args[0], args[1], conn);
249 NODE_HTTP_CLIENT_RESPONSE(&conn, conn.remote, conn.port, conn.fd);
250 }
251
dtrace_gc_start(Isolate * isolate,GCType type,GCCallbackFlags flags,void * data)252 void dtrace_gc_start(Isolate* isolate,
253 GCType type,
254 GCCallbackFlags flags,
255 void* data) {
256 // Previous versions of this probe point only logged type and flags.
257 // That's why for reasons of backwards compatibility the isolate goes last.
258 NODE_GC_START(type, flags, isolate);
259 }
260
dtrace_gc_done(Isolate * isolate,GCType type,GCCallbackFlags flags,void * data)261 void dtrace_gc_done(Isolate* isolate,
262 GCType type,
263 GCCallbackFlags flags,
264 void* data) {
265 // Previous versions of this probe point only logged type and flags.
266 // That's why for reasons of backwards compatibility the isolate goes last.
267 NODE_GC_DONE(type, flags, isolate);
268 }
269
270
InitDTrace(Environment * env)271 void InitDTrace(Environment* env) {
272 #ifdef HAVE_ETW
273 // ETW is neither thread-safe nor does it clean up resources on exit,
274 // so we can use it only on the main thread.
275 if (env->is_main_thread()) {
276 init_etw();
277 }
278 #endif
279
280 // We need to use the variant of GC callbacks that takes data to
281 // avoid running into DCHECKs when multiple Environments try to add
282 // the same callback to the same isolate multiple times.
283 env->isolate()->AddGCPrologueCallback(dtrace_gc_start, env);
284 env->isolate()->AddGCEpilogueCallback(dtrace_gc_done, env);
285 env->AddCleanupHook([](void* data) {
286 Environment* env = static_cast<Environment*>(data);
287 env->isolate()->RemoveGCPrologueCallback(dtrace_gc_start, env);
288 env->isolate()->RemoveGCEpilogueCallback(dtrace_gc_done, env);
289 }, env);
290 }
291
292 #define NODE_PROBES(V) \
293 V(DTRACE_NET_SERVER_CONNECTION) \
294 V(DTRACE_NET_STREAM_END) \
295 V(DTRACE_HTTP_SERVER_REQUEST) \
296 V(DTRACE_HTTP_SERVER_RESPONSE) \
297 V(DTRACE_HTTP_CLIENT_REQUEST) \
298 V(DTRACE_HTTP_CLIENT_RESPONSE)
299
InitializeDTrace(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)300 void InitializeDTrace(Local<Object> target,
301 Local<Value> unused,
302 Local<Context> context,
303 void* priv) {
304 #if defined HAVE_DTRACE || defined HAVE_ETW
305 #define V(name) SetMethod(context, target, #name, name);
306 NODE_PROBES(V)
307 #undef V
308 #endif // defined HAVE_DTRACE || defined HAVE_ETW
309 }
310
RegisterDtraceExternalReferences(ExternalReferenceRegistry * registry)311 void RegisterDtraceExternalReferences(ExternalReferenceRegistry* registry) {
312 #if defined HAVE_DTRACE || defined HAVE_ETW
313 #define V(name) registry->Register(name);
314 NODE_PROBES(V)
315 #undef V
316 #endif // defined HAVE_DTRACE || defined HAVE_ETW
317 }
318
319 } // namespace node
320 NODE_BINDING_CONTEXT_AWARE_INTERNAL(dtrace, node::InitializeDTrace)
321 NODE_BINDING_EXTERNAL_REFERENCE(dtrace, node::RegisterDtraceExternalReferences)
322