1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "IOActivityMonitor.h"
7 #include "nsPrintfCString.h"
8 #include "nsSocketTransport2.h"
9 #include "nsSocketTransportService2.h"
10 #include "nsThreadUtils.h"
11 #include "mozilla/dom/Promise.h"
12 #include "prerror.h"
13 #include "prio.h"
14 #include "prmem.h"
15 #include <vector>
16 
17 using namespace mozilla;
18 using namespace mozilla::net;
19 
20 mozilla::StaticRefPtr<IOActivityMonitor> gInstance;
21 static PRDescIdentity sNetActivityMonitorLayerIdentity;
22 static PRIOMethods sNetActivityMonitorLayerMethods;
23 static PRIOMethods* sNetActivityMonitorLayerMethodsPtr = nullptr;
24 
25 // Maximum number of activities entries in the monitoring class
26 #define MAX_ACTIVITY_ENTRIES 1000
27 
28 // ActivityMonitorSecret is stored in the activity monitor layer
29 // and provides a method to get the location.
30 //
31 // A location can be :
32 // - a TCP or UDP socket. The form will be socket://ip:port
33 // - a File. The form will be file://path
34 //
35 // For other cases, the location will be fd://number
36 class ActivityMonitorSecret final {
37  public:
38   // constructor used for sockets
ActivityMonitorSecret(PRFileDesc * aFd)39   explicit ActivityMonitorSecret(PRFileDesc* aFd) {
40     mFd = aFd;
41     mLocationSet = false;
42   }
43 
44   // constructor used for files
ActivityMonitorSecret(PRFileDesc * aFd,const char * aLocation)45   explicit ActivityMonitorSecret(PRFileDesc* aFd, const char* aLocation) {
46     mFd = aFd;
47     mLocation.AppendPrintf("file://%s", aLocation);
48     mLocationSet = true;
49   }
50 
getLocation()51   nsCString getLocation() {
52     if (!mLocationSet) {
53       LazySetLocation();
54     }
55     return mLocation;
56   }
57 
58  private:
59   // Called to set the location using the FD on the first getLocation() usage
60   // which is typically when a socket is opened. If done earlier, at
61   // construction time, the host won't be bound yet.
62   //
63   // If the location is a file, it needs to be initialized in the
64   // constructor.
LazySetLocation()65   void LazySetLocation() {
66     mLocationSet = true;
67     PRFileDesc* extract = mFd;
68     while (PR_GetDescType(extract) == PR_DESC_LAYERED) {
69       if (!extract->lower) {
70         break;
71       }
72       extract = extract->lower;
73     }
74 
75     PRDescType fdType = PR_GetDescType(extract);
76     // we should not use LazySetLocation for files
77     MOZ_ASSERT(fdType != PR_DESC_FILE);
78 
79     switch (fdType) {
80       case PR_DESC_SOCKET_TCP:
81       case PR_DESC_SOCKET_UDP: {
82         mLocation.AppendPrintf("socket://");
83         PRNetAddr addr;
84         PRStatus status = PR_GetSockName(mFd, &addr);
85         if (NS_WARN_IF(status == PR_FAILURE)) {
86           mLocation.AppendPrintf("unknown");
87           break;
88         }
89 
90         // grabbing the host
91         char netAddr[mozilla::net::kNetAddrMaxCStrBufSize] = {0};
92         status = PR_NetAddrToString(&addr, netAddr, sizeof(netAddr) - 1);
93         if (NS_WARN_IF(status == PR_FAILURE) || netAddr[0] == 0) {
94           mLocation.AppendPrintf("unknown");
95           break;
96         }
97         mLocation.Append(netAddr);
98 
99         // adding the port
100         uint16_t port;
101         if (addr.raw.family == PR_AF_INET) {
102           port = addr.inet.port;
103         } else {
104           port = addr.ipv6.port;
105         }
106         mLocation.AppendPrintf(":%d", port);
107       } break;
108 
109       // for all other cases, we just send back fd://<value>
110       default: {
111         mLocation.AppendPrintf("fd://%d", PR_FileDesc2NativeHandle(mFd));
112       }
113     }  // end switch
114   }
115 
116  private:
117   nsCString mLocation;
118   bool mLocationSet;
119   PRFileDesc* mFd;
120 };
121 
122 // FileDesc2Location converts a PRFileDesc into a "location" by
123 // grabbing the ActivityMonitorSecret in layer->secret
FileDesc2Location(PRFileDesc * fd)124 static nsAutoCString FileDesc2Location(PRFileDesc* fd) {
125   nsAutoCString location;
126   PRFileDesc* monitorLayer =
127       PR_GetIdentitiesLayer(fd, sNetActivityMonitorLayerIdentity);
128   if (!monitorLayer) {
129     location.AppendPrintf("unknown");
130     return location;
131   }
132 
133   ActivityMonitorSecret* secret = (ActivityMonitorSecret*)monitorLayer->secret;
134   location.AppendPrintf("%s", secret->getLocation().get());
135   return location;
136 }
137 
138 //
139 // Wrappers around the socket APIS
140 //
nsNetMon_Connect(PRFileDesc * fd,const PRNetAddr * addr,PRIntervalTime timeout)141 static PRStatus nsNetMon_Connect(PRFileDesc* fd, const PRNetAddr* addr,
142                                  PRIntervalTime timeout) {
143   return fd->lower->methods->connect(fd->lower, addr, timeout);
144 }
145 
nsNetMon_Close(PRFileDesc * fd)146 static PRStatus nsNetMon_Close(PRFileDesc* fd) {
147   if (!fd) {
148     return PR_FAILURE;
149   }
150   PRFileDesc* layer = PR_PopIOLayer(fd, PR_TOP_IO_LAYER);
151   MOZ_RELEASE_ASSERT(
152       layer && layer->identity == sNetActivityMonitorLayerIdentity,
153       "NetActivityMonitor Layer not on top of stack");
154 
155   if (layer->secret) {
156     delete (ActivityMonitorSecret*)layer->secret;
157     layer->secret = nullptr;
158   }
159   layer->dtor(layer);
160   return fd->methods->close(fd);
161 }
162 
nsNetMon_Read(PRFileDesc * fd,void * buf,int32_t len)163 static int32_t nsNetMon_Read(PRFileDesc* fd, void* buf, int32_t len) {
164   int32_t ret = fd->lower->methods->read(fd->lower, buf, len);
165   if (ret >= 0) {
166     IOActivityMonitor::Read(fd, len);
167   }
168   return ret;
169 }
170 
nsNetMon_Write(PRFileDesc * fd,const void * buf,int32_t len)171 static int32_t nsNetMon_Write(PRFileDesc* fd, const void* buf, int32_t len) {
172   int32_t ret = fd->lower->methods->write(fd->lower, buf, len);
173   if (ret > 0) {
174     IOActivityMonitor::Write(fd, len);
175   }
176   return ret;
177 }
178 
nsNetMon_Writev(PRFileDesc * fd,const PRIOVec * iov,int32_t size,PRIntervalTime timeout)179 static int32_t nsNetMon_Writev(PRFileDesc* fd, const PRIOVec* iov, int32_t size,
180                                PRIntervalTime timeout) {
181   int32_t ret = fd->lower->methods->writev(fd->lower, iov, size, timeout);
182   if (ret > 0) {
183     IOActivityMonitor::Write(fd, size);
184   }
185   return ret;
186 }
187 
nsNetMon_Recv(PRFileDesc * fd,void * buf,int32_t amount,int flags,PRIntervalTime timeout)188 static int32_t nsNetMon_Recv(PRFileDesc* fd, void* buf, int32_t amount,
189                              int flags, PRIntervalTime timeout) {
190   int32_t ret =
191       fd->lower->methods->recv(fd->lower, buf, amount, flags, timeout);
192   if (ret > 0) {
193     IOActivityMonitor::Read(fd, amount);
194   }
195   return ret;
196 }
197 
nsNetMon_Send(PRFileDesc * fd,const void * buf,int32_t amount,int flags,PRIntervalTime timeout)198 static int32_t nsNetMon_Send(PRFileDesc* fd, const void* buf, int32_t amount,
199                              int flags, PRIntervalTime timeout) {
200   int32_t ret =
201       fd->lower->methods->send(fd->lower, buf, amount, flags, timeout);
202   if (ret > 0) {
203     IOActivityMonitor::Write(fd, amount);
204   }
205   return ret;
206 }
207 
nsNetMon_RecvFrom(PRFileDesc * fd,void * buf,int32_t amount,int flags,PRNetAddr * addr,PRIntervalTime timeout)208 static int32_t nsNetMon_RecvFrom(PRFileDesc* fd, void* buf, int32_t amount,
209                                  int flags, PRNetAddr* addr,
210                                  PRIntervalTime timeout) {
211   int32_t ret = fd->lower->methods->recvfrom(fd->lower, buf, amount, flags,
212                                              addr, timeout);
213   if (ret > 0) {
214     IOActivityMonitor::Read(fd, amount);
215   }
216   return ret;
217 }
218 
nsNetMon_SendTo(PRFileDesc * fd,const void * buf,int32_t amount,int flags,const PRNetAddr * addr,PRIntervalTime timeout)219 static int32_t nsNetMon_SendTo(PRFileDesc* fd, const void* buf, int32_t amount,
220                                int flags, const PRNetAddr* addr,
221                                PRIntervalTime timeout) {
222   int32_t ret =
223       fd->lower->methods->sendto(fd->lower, buf, amount, flags, addr, timeout);
224   if (ret > 0) {
225     IOActivityMonitor::Write(fd, amount);
226   }
227   return ret;
228 }
229 
nsNetMon_AcceptRead(PRFileDesc * listenSock,PRFileDesc ** acceptedSock,PRNetAddr ** peerAddr,void * buf,int32_t amount,PRIntervalTime timeout)230 static int32_t nsNetMon_AcceptRead(PRFileDesc* listenSock,
231                                    PRFileDesc** acceptedSock,
232                                    PRNetAddr** peerAddr, void* buf,
233                                    int32_t amount, PRIntervalTime timeout) {
234   int32_t ret = listenSock->lower->methods->acceptread(
235       listenSock->lower, acceptedSock, peerAddr, buf, amount, timeout);
236   if (ret > 0) {
237     IOActivityMonitor::Read(listenSock, amount);
238   }
239   return ret;
240 }
241 
242 //
243 // Class IOActivityMonitor
244 //
NS_IMPL_ISUPPORTS(IOActivityMonitor,nsINamed)245 NS_IMPL_ISUPPORTS(IOActivityMonitor, nsINamed)
246 
247 IOActivityMonitor::IOActivityMonitor() : mLock("IOActivityMonitor::mLock") {
248   RefPtr<IOActivityMonitor> mon(gInstance);
249   MOZ_ASSERT(!mon, "multiple IOActivityMonitor instances!");
250 }
251 
252 // static
RequestActivities(dom::Promise * aPromise)253 void IOActivityMonitor::RequestActivities(dom::Promise* aPromise) {
254   MOZ_ASSERT(aPromise);
255   RefPtr<IOActivityMonitor> mon(gInstance);
256   if (!IsActive()) {
257     aPromise->MaybeReject(NS_ERROR_FAILURE);
258     return;
259   }
260   mon->RequestActivitiesInternal(aPromise);
261 }
262 
RequestActivitiesInternal(dom::Promise * aPromise)263 void IOActivityMonitor::RequestActivitiesInternal(dom::Promise* aPromise) {
264   nsresult result = NS_OK;
265   FallibleTArray<dom::IOActivityDataDictionary> activities;
266 
267   {
268     mozilla::MutexAutoLock lock(mLock);
269     // Remove inactive activities
270     for (auto iter = mActivities.Iter(); !iter.Done(); iter.Next()) {
271       dom::IOActivityDataDictionary* activity = &iter.Data();
272       if (activity->mRx == 0 && activity->mTx == 0) {
273         iter.Remove();
274       } else {
275         if (NS_WARN_IF(!activities.AppendElement(iter.Data(), fallible))) {
276           result = NS_ERROR_OUT_OF_MEMORY;
277           break;
278         }
279       }
280     }
281   }
282 
283   if (NS_WARN_IF(NS_FAILED(result))) {
284     aPromise->MaybeReject(result);
285     return;
286   }
287   aPromise->MaybeResolve(activities);
288 }
289 
290 // static
291 NS_IMETHODIMP
GetName(nsACString & aName)292 IOActivityMonitor::GetName(nsACString& aName) {
293   aName.AssignLiteral("IOActivityMonitor");
294   return NS_OK;
295 }
296 
IsActive()297 bool IOActivityMonitor::IsActive() { return gInstance != nullptr; }
298 
Init()299 nsresult IOActivityMonitor::Init() {
300   if (IsActive()) {
301     return NS_ERROR_ALREADY_INITIALIZED;
302   }
303   RefPtr<IOActivityMonitor> mon = new IOActivityMonitor();
304   nsresult rv = mon->InitInternal();
305   if (NS_SUCCEEDED(rv)) {
306     gInstance = mon;
307   }
308   return rv;
309 }
310 
InitInternal()311 nsresult IOActivityMonitor::InitInternal() {
312   // wraps the socket APIs
313   if (!sNetActivityMonitorLayerMethodsPtr) {
314     sNetActivityMonitorLayerIdentity =
315         PR_GetUniqueIdentity("network activity monitor layer");
316     sNetActivityMonitorLayerMethods = *PR_GetDefaultIOMethods();
317     sNetActivityMonitorLayerMethods.connect = nsNetMon_Connect;
318     sNetActivityMonitorLayerMethods.read = nsNetMon_Read;
319     sNetActivityMonitorLayerMethods.write = nsNetMon_Write;
320     sNetActivityMonitorLayerMethods.writev = nsNetMon_Writev;
321     sNetActivityMonitorLayerMethods.recv = nsNetMon_Recv;
322     sNetActivityMonitorLayerMethods.send = nsNetMon_Send;
323     sNetActivityMonitorLayerMethods.recvfrom = nsNetMon_RecvFrom;
324     sNetActivityMonitorLayerMethods.sendto = nsNetMon_SendTo;
325     sNetActivityMonitorLayerMethods.acceptread = nsNetMon_AcceptRead;
326     sNetActivityMonitorLayerMethods.close = nsNetMon_Close;
327     sNetActivityMonitorLayerMethodsPtr = &sNetActivityMonitorLayerMethods;
328   }
329 
330   return NS_OK;
331 }
332 
Shutdown()333 nsresult IOActivityMonitor::Shutdown() {
334   RefPtr<IOActivityMonitor> mon(gInstance);
335   if (!mon) {
336     return NS_ERROR_NOT_INITIALIZED;
337   }
338   return mon->ShutdownInternal();
339 }
340 
ShutdownInternal()341 nsresult IOActivityMonitor::ShutdownInternal() {
342   mozilla::MutexAutoLock lock(mLock);
343   mActivities.Clear();
344   gInstance = nullptr;
345   return NS_OK;
346 }
347 
MonitorSocket(PRFileDesc * aFd)348 nsresult IOActivityMonitor::MonitorSocket(PRFileDesc* aFd) {
349   RefPtr<IOActivityMonitor> mon(gInstance);
350   if (!IsActive()) {
351     return NS_OK;
352   }
353   PRFileDesc* layer;
354   PRStatus status;
355   layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity,
356                                sNetActivityMonitorLayerMethodsPtr);
357   if (!layer) {
358     return NS_ERROR_FAILURE;
359   }
360 
361   ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd);
362   layer->secret = reinterpret_cast<PRFilePrivate*>(secret);
363   status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
364 
365   if (status == PR_FAILURE) {
366     delete secret;
367     PR_Free(layer);  // PR_CreateIOLayerStub() uses PR_Malloc().
368     return NS_ERROR_FAILURE;
369   }
370   return NS_OK;
371 }
372 
MonitorFile(PRFileDesc * aFd,const char * aPath)373 nsresult IOActivityMonitor::MonitorFile(PRFileDesc* aFd, const char* aPath) {
374   RefPtr<IOActivityMonitor> mon(gInstance);
375   if (!IsActive()) {
376     return NS_OK;
377   }
378   PRFileDesc* layer;
379   PRStatus status;
380   layer = PR_CreateIOLayerStub(sNetActivityMonitorLayerIdentity,
381                                sNetActivityMonitorLayerMethodsPtr);
382   if (!layer) {
383     return NS_ERROR_FAILURE;
384   }
385 
386   ActivityMonitorSecret* secret = new ActivityMonitorSecret(aFd, aPath);
387   layer->secret = reinterpret_cast<PRFilePrivate*>(secret);
388 
389   status = PR_PushIOLayer(aFd, PR_NSPR_IO_LAYER, layer);
390   if (status == PR_FAILURE) {
391     delete secret;
392     PR_Free(layer);  // PR_CreateIOLayerStub() uses PR_Malloc().
393     return NS_ERROR_FAILURE;
394   }
395 
396   return NS_OK;
397 }
398 
IncrementActivity(const nsACString & aLocation,uint32_t aRx,uint32_t aTx)399 bool IOActivityMonitor::IncrementActivity(const nsACString& aLocation,
400                                           uint32_t aRx, uint32_t aTx) {
401   mLock.AssertCurrentThreadOwns();
402   return mActivities.WithEntryHandle(aLocation, fallible, [&](auto&& entry) {
403     if (!entry) return false;
404 
405     if (*entry) {
406       // already registered
407       entry->Data().mTx += aTx;
408       entry->Data().mRx += aRx;
409     } else {
410       // Creating a new IOActivity. Notice that mActivities
411       // will grow indefinitely, which is OK since we won't
412       // have but a few hundreds entries at the most, but we
413       // want to assert we have at the most 1000 entries
414       MOZ_ASSERT(mActivities.Count() <= MAX_ACTIVITY_ENTRIES);
415 
416       dom::IOActivityDataDictionary activity;
417       activity.mLocation.Assign(aLocation);
418       activity.mTx = aTx;
419       activity.mRx = aRx;
420 
421       entry->Insert(std::move(activity));
422     }
423 
424     return true;
425   });
426 }
427 
Write(const nsACString & aLocation,uint32_t aAmount)428 nsresult IOActivityMonitor::Write(const nsACString& aLocation,
429                                   uint32_t aAmount) {
430   RefPtr<IOActivityMonitor> mon(gInstance);
431   if (!mon) {
432     return NS_ERROR_FAILURE;
433   }
434   return mon->WriteInternal(aLocation, aAmount);
435 }
436 
Write(PRFileDesc * fd,uint32_t aAmount)437 nsresult IOActivityMonitor::Write(PRFileDesc* fd, uint32_t aAmount) {
438   RefPtr<IOActivityMonitor> mon(gInstance);
439   if (!mon) {
440     return NS_ERROR_FAILURE;
441   }
442   return mon->Write(FileDesc2Location(fd), aAmount);
443 }
444 
WriteInternal(const nsACString & aLocation,uint32_t aAmount)445 nsresult IOActivityMonitor::WriteInternal(const nsACString& aLocation,
446                                           uint32_t aAmount) {
447   mozilla::MutexAutoLock lock(mLock);
448   if (!IncrementActivity(aLocation, aAmount, 0)) {
449     return NS_ERROR_FAILURE;
450   }
451   return NS_OK;
452 }
453 
Read(PRFileDesc * fd,uint32_t aAmount)454 nsresult IOActivityMonitor::Read(PRFileDesc* fd, uint32_t aAmount) {
455   RefPtr<IOActivityMonitor> mon(gInstance);
456   if (!mon) {
457     return NS_ERROR_FAILURE;
458   }
459   return mon->Read(FileDesc2Location(fd), aAmount);
460 }
461 
Read(const nsACString & aLocation,uint32_t aAmount)462 nsresult IOActivityMonitor::Read(const nsACString& aLocation,
463                                  uint32_t aAmount) {
464   RefPtr<IOActivityMonitor> mon(gInstance);
465   if (!mon) {
466     return NS_ERROR_FAILURE;
467   }
468   return mon->ReadInternal(aLocation, aAmount);
469 }
470 
ReadInternal(const nsACString & aLocation,uint32_t aAmount)471 nsresult IOActivityMonitor::ReadInternal(const nsACString& aLocation,
472                                          uint32_t aAmount) {
473   mozilla::MutexAutoLock lock(mLock);
474   if (!IncrementActivity(aLocation, 0, aAmount)) {
475     return NS_ERROR_FAILURE;
476   }
477   return NS_OK;
478 }
479