Bug 1310703 - Introduce the pingsender executable; r?ted
MozReview-Commit-ID: HxU41s0yamZ
--- a/browser/app/macbuild/Contents/MacOS-files.in
+++ b/browser/app/macbuild/Contents/MacOS-files.in
@@ -1,9 +1,10 @@
/*.app/***
/*.dylib
/certutil
/firefox-bin
/gtest/***
+/pingsender
/pk12util
/ssltunnel
/xpcshell
/XUL
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -765,28 +765,32 @@ bin/libfreebl_32int64_3.so
#ifdef MOZ_CRASHREPORTER
@RESPATH@/components/CrashService.manifest
@RESPATH@/components/CrashService.js
@RESPATH@/components/toolkit_crashservice.xpt
#ifdef XP_MACOSX
@BINPATH@/crashreporter.app/
#else
@BINPATH@/crashreporter@BIN_SUFFIX@
+@RESPATH@/crashreporter.ini
@BINPATH@/minidump-analyzer@BIN_SUFFIX@
-@RESPATH@/crashreporter.ini
#ifdef XP_UNIX
@RESPATH@/Throbber-small.gif
#endif
#endif
@RESPATH@/browser/crashreporter-override.ini
#ifdef MOZ_CRASHREPORTER_INJECTOR
@BINPATH@/breakpadinjector.dll
#endif
#endif
+; [ Ping Sender ]
+;
+@BINPATH@/pingsender@BIN_SUFFIX@
+
@RESPATH@/components/dom_audiochannel.xpt
; Shutdown Terminator
@RESPATH@/components/nsTerminatorTelemetry.js
@RESPATH@/components/terminator.manifest
#if defined(CLANG_CXX)
#if defined(MOZ_ASAN) || defined(MOZ_TSAN)
--- a/browser/installer/windows/nsis/shared.nsh
+++ b/browser/installer/windows/nsis/shared.nsh
@@ -1130,16 +1130,17 @@
Push "freebl3.dll"
Push "nssckbi.dll"
Push "nspr4.dll"
Push "nssdbm3.dll"
Push "mozsqlite3.dll"
Push "xpcom.dll"
Push "crashreporter.exe"
Push "minidump-analyzer.exe"
+ Push "pingsender.exe"
Push "updater.exe"
Push "${FileMainEXE}"
!macroend
!define PushFilesToCheck "!insertmacro PushFilesToCheck"
; Pushes the string "true" to the top of the stack if the Firewall service is
; running and pushes the string "false" to the top of the stack if it isn't.
--- a/python/mozbuild/mozbuild/artifacts.py
+++ b/python/mozbuild/mozbuild/artifacts.py
@@ -262,16 +262,17 @@ class LinuxArtifactJob(ArtifactJob):
package_artifact_patterns = {
'firefox/application.ini',
'firefox/crashreporter',
'firefox/dependentlibs.list',
'firefox/firefox',
'firefox/firefox-bin',
'firefox/minidump-analyzer',
+ 'firefox/pingsender',
'firefox/platform.ini',
'firefox/plugin-container',
'firefox/updater',
'firefox/**/*.so',
'firefox/**/interfaces.xpt',
}
def process_package_artifact(self, filename, processed_filename):
@@ -347,16 +348,17 @@ class MacArtifactJob(ArtifactJob):
'libnssckbi.dylib',
'libnssdbm3.dylib',
'libplugin_child_interpose.dylib',
# 'libreplace_jemalloc.dylib',
# 'libreplace_malloc.dylib',
'libmozavutil.dylib',
'libmozavcodec.dylib',
'libsoftokn3.dylib',
+ 'pingsender',
'plugin-container.app/Contents/MacOS/plugin-container',
'updater.app/Contents/MacOS/org.mozilla.updater',
# 'xpcshell',
'XUL',
])
# These get copied into dist/bin with the path, so "root/a/b/c" -> "dist/bin/a/b/c".
paths_keep_path = ('Contents/Resources', [
--- a/toolkit/components/telemetry/docs/internals/index.rst
+++ b/toolkit/components/telemetry/docs/internals/index.rst
@@ -2,8 +2,9 @@
Internals
=========
.. toctree::
:maxdepth: 2
:titlesonly:
preferences
+ pingsender
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/docs/internals/pingsender.rst
@@ -0,0 +1,22 @@
+Ping Sender
+===========
+
+The ping sender is a minimalistic program whose sole purpose is to deliver a
+telemetry ping. It accepts a single parameter which is the URL the ping will
+be sent to (as an HTTP POST command) and once started it will wait to read the
+ping contents on its stdin stream. Once the ping has been read from stdin the
+ping sender will try to post it once, exiting with a non-zero value if it
+fails.
+
+The ping sender relies on libcurl for Linux and Mac build and on WinInet for
+Windows ones for its HTTP functionality. It currently ignores Firefox or the
+system proxy configuration.
+
+In non-debug mode the ping sender doesn't print anything, not even on error,
+this is done deliberately to prevent startling the user on architectures such
+as Windows that would open a seperate console window just to display the
+program output. If you need runtime information to be printed out compile the
+ping sender with debuggging enabled.
+
+The pingsender is not supported on Firefox for Android at the moment
+(see `bug 1335917 <https://bugzilla.mozilla.org/show_bug.cgi?id=1335917>`_)
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -5,16 +5,20 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
HAS_MISC_RULE = True
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
+DIRS = [
+ 'pingsender',
+]
+
DEFINES['MOZ_APP_VERSION'] = '"%s"' % CONFIG['MOZ_APP_VERSION']
LOCAL_INCLUDES += [
'/xpcom/build',
'/xpcom/threads',
]
SPHINX_TREES['telemetry'] = 'docs'
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/pingsender/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['OS_TARGET'] != 'Android':
+ Program('pingsender')
+
+ UNIFIED_SOURCES += [
+ 'pingsender.cpp',
+ ]
+
+ LOCAL_INCLUDES += [
+ '/toolkit/crashreporter/google-breakpad/src',
+ ]
+
+ if CONFIG['OS_TARGET'] == 'WINNT':
+ OS_LIBS += [
+ 'wininet',
+ ]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ UNIFIED_SOURCES += [
+ 'pingsender_win.cpp',
+ ]
+elif CONFIG['OS_ARCH'] == 'Darwin' or CONFIG['OS_ARCH'] == 'Linux':
+ UNIFIED_SOURCES += [
+ 'pingsender_unix_common.cpp',
+ ]
+
+# Don't use the STL wrappers; we don't link with -lmozalloc, and it really
+# doesn't matter here anyway.
+DISABLE_STL_WRAPPING = True
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/pingsender/pingsender.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <cstdlib>
+#include <string>
+#include <cstdio>
+
+#include "pingsender.h"
+
+using std::string;
+
+namespace PingSender {
+
+const char* kUserAgent = "pingsender/1.0";
+
+/**
+ * Read the ping contents from stdin as a string
+ */
+static std::string ReadPingFromStdin()
+{
+ const size_t kBufferSize = 32768;
+ char buff[kBufferSize];
+ string ping;
+ size_t readBytes = 0;
+
+ do {
+ readBytes = fread(buff, 1, kBufferSize, stdin);
+ ping.append(buff, readBytes);
+ } while (!feof(stdin) && !ferror(stdin));
+
+ if (ferror(stdin)) {
+ PINGSENDER_LOG("ERROR: Could not read from stdin\n");
+ return "";
+ }
+
+ return ping;
+}
+
+} // namespace PingSender
+
+using namespace PingSender;
+
+int main(int argc, char* argv[])
+{
+ string url;
+
+ if (argc == 2) {
+ url = argv[1];
+ } else {
+ PINGSENDER_LOG("Usage: pingsender URL\n"
+ "Send the payload passed via stdin to the specified URL "
+ "using an HTTP POST message\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (url.empty()) {
+ PINGSENDER_LOG("ERROR: No URL was provided\n");
+ exit(EXIT_FAILURE);
+ }
+
+ string ping(ReadPingFromStdin());
+
+ if (ping.empty()) {
+ PINGSENDER_LOG("ERROR: Ping payload is empty\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (!Post(url, ping)) {
+ exit(EXIT_FAILURE);
+ }
+
+ exit(EXIT_SUCCESS);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/pingsender/pingsender.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#ifdef DEBUG
+#define PINGSENDER_LOG(s, ...) printf(s, ##__VA_ARGS__)
+#else
+#define PINGSENDER_LOG(s, ...)
+#endif // DEBUG
+
+namespace PingSender {
+
+// System-specific function to make an HTTP POST operation
+bool Post(const std::string& url, const std::string& payload);
+
+} // namespace PingSender
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/pingsender/pingsender_unix_common.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <cerrno>
+#include <cstring>
+#include <string>
+
+#include <dlfcn.h>
+#include <unistd.h>
+
+#include "third_party/curl/curl.h"
+
+namespace PingSender {
+
+using std::string;
+
+/**
+ * A simple wrapper around libcurl "easy" functions. Provides RAII opening
+ * and initialization of the curl library
+ */
+class CurlWrapper
+{
+public:
+ CurlWrapper();
+ ~CurlWrapper();
+ bool Init();
+ bool Post(const string& url, const string& payload);
+
+ // libcurl functions
+ CURL* (*easy_init)(void);
+ CURLcode (*easy_setopt)(CURL*, CURLoption, ...);
+ CURLcode (*easy_perform)(CURL*);
+ CURLcode (*easy_getinfo)(CURL*, CURLINFO, ...);
+ const char* (*easy_strerror)(CURLcode);
+ void (*easy_cleanup)(CURL*);
+
+private:
+ void* mLib;
+ void* mCurl;
+};
+
+CurlWrapper::CurlWrapper()
+ : easy_init(nullptr)
+ , easy_setopt(nullptr)
+ , easy_perform(nullptr)
+ , easy_getinfo(nullptr)
+ , easy_strerror(nullptr)
+ , easy_cleanup(nullptr)
+ , mLib(nullptr)
+ , mCurl(nullptr)
+{}
+
+CurlWrapper::~CurlWrapper()
+{
+ if(mLib) {
+ if(mCurl && easy_cleanup) {
+ easy_cleanup(mCurl);
+ }
+
+ dlclose(mLib);
+ }
+}
+
+bool
+CurlWrapper::Init()
+{
+ // libcurl might show up under different names, try them all until we find it
+ const char* libcurlNames[] = {
+ "libcurl.so",
+ "libcurl.so.4",
+ // Debian gives libcurl a different name when it is built against GnuTLS
+ "libcurl-gnutls.so.4",
+ // Older libcurl if we can't find anything better
+ "libcurl.so.3",
+#ifndef HAVE_64BIT_BUILD
+ // 32-bit versions on 64-bit hosts
+ "/usr/lib32/libcurl.so",
+ "/usr/lib32/libcurl.so.4",
+ "/usr/lib32/libcurl-gnutls.so.4",
+ "/usr/lib32/libcurl.so.3",
+#endif
+ // macOS
+ "libcurl.dylib",
+ "libcurl.4.dylib",
+ "libcurl.3.dylib"
+ };
+
+ for (const char* libname : libcurlNames) {
+ mLib = dlopen(libname, RTLD_NOW);
+
+ if (mLib) {
+ break;
+ }
+ }
+
+ if (!mLib) {
+ PINGSENDER_LOG("ERROR: Could not find libcurl\n");
+ return false;
+ }
+
+ *(void**) (&easy_init) = dlsym(mLib, "curl_easy_init");
+ *(void**) (&easy_setopt) = dlsym(mLib, "curl_easy_setopt");
+ *(void**) (&easy_perform) = dlsym(mLib, "curl_easy_perform");
+ *(void**) (&easy_getinfo) = dlsym(mLib, "curl_easy_getinfo");
+ *(void**) (&easy_strerror) = dlsym(mLib, "curl_easy_strerror");
+ *(void**) (&easy_cleanup) = dlsym(mLib, "curl_easy_cleanup");
+
+ if (!easy_init ||
+ !easy_setopt ||
+ !easy_perform ||
+ !easy_getinfo ||
+ !easy_strerror ||
+ !easy_cleanup) {
+ PINGSENDER_LOG("ERROR: libcurl is missing one of the required symbols\n");
+ return false;
+ }
+
+ mCurl = easy_init();
+
+ if (!mCurl) {
+ PINGSENDER_LOG("ERROR: Could not initialize libcurl\n");
+ return false;
+ }
+
+ return true;
+}
+
+bool
+CurlWrapper::Post(const string& url, const string& payload)
+{
+ easy_setopt(mCurl, CURLOPT_URL, url.c_str());
+ easy_setopt(mCurl, CURLOPT_USERAGENT, kUserAgent);
+
+ // Set the size of the POST data
+ easy_setopt(mCurl, CURLOPT_POSTFIELDSIZE, payload.length());
+
+ // Set the contents of the POST data
+ easy_setopt(mCurl, CURLOPT_POSTFIELDS, payload.c_str());
+
+ // Fail if the server returns a 4xx code
+ easy_setopt(mCurl, CURLOPT_FAILONERROR, 1);
+
+ // Block until the operation is performend. Ignore the response, if the POST
+ // fails we can't do anything about it.
+ CURLcode err = easy_perform(mCurl);
+
+ if (err != CURLE_OK) {
+ PINGSENDER_LOG("ERROR: Failed to send HTTP request, %s\n",
+ easy_strerror(err));
+ return false;
+ }
+
+ return true;
+}
+
+bool
+Post(const string& url, const string& payload)
+{
+ CurlWrapper curl;
+
+ if (!curl.Init()) {
+ return false;
+ }
+
+ return curl.Post(url, payload);
+}
+
+} // namespace PingSender
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/pingsender/pingsender_win.cpp
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include <windows.h>
+#include <wininet.h>
+
+namespace PingSender {
+
+using std::string;
+
+/**
+ * A helper to automatically close internet handles when they go out of scope
+ */
+class ScopedHInternet {
+public:
+ explicit ScopedHInternet(HINTERNET handle) : mHandle(handle) {}
+
+ ~ScopedHInternet() {
+ if (mHandle) {
+ InternetCloseHandle(mHandle);
+ }
+ }
+
+ bool empty() { return (mHandle == nullptr); }
+ HINTERNET get() { return mHandle; }
+
+private:
+ HINTERNET mHandle;
+};
+
+const size_t kUrlComponentsSchemeLength = 256;
+const size_t kUrlComponentsHostLength = 256;
+const size_t kUrlComponentsPathLength = 256;
+
+/**
+ * Post the specified payload to a telemetry server
+ *
+ * @param url The URL of the telemetry server
+ * @param payload The ping payload
+ */
+bool
+Post(const string& url, const string& payload)
+{
+ char scheme[kUrlComponentsSchemeLength];
+ char host[kUrlComponentsHostLength];
+ char path[kUrlComponentsPathLength];
+
+ URL_COMPONENTS components = {};
+ components.dwStructSize = sizeof(components);
+ components.lpszScheme = scheme;
+ components.dwSchemeLength = kUrlComponentsSchemeLength;
+ components.lpszHostName = host;
+ components.dwHostNameLength = kUrlComponentsHostLength;
+ components.lpszUrlPath = path;
+ components.dwUrlPathLength = kUrlComponentsPathLength;
+
+ if (!InternetCrackUrl(url.c_str(), url.size(), 0, &components)) {
+ PINGSENDER_LOG("ERROR: Could not separate the URL components\n");
+ return false;
+ }
+
+ ScopedHInternet internet(InternetOpen(kUserAgent,
+ INTERNET_OPEN_TYPE_PRECONFIG,
+ /* lpszProxyName */ NULL,
+ /* lpszProxyBypass */ NULL,
+ /* dwFlags */ 0));
+
+ if (internet.empty()) {
+ PINGSENDER_LOG("ERROR: Could not open wininet internet handle\n");
+ return false;
+ }
+
+ ScopedHInternet connection(InternetConnect(internet.get(),
+ host, components.nPort,
+ /* lpszUsername */ NULL,
+ /* lpszPassword */ NULL,
+ INTERNET_SERVICE_HTTP,
+ /* dwFlags */ 0,
+ /* dwContext */ NULL));
+
+ if (connection.empty()) {
+ PINGSENDER_LOG("ERROR: Could not connect\n");
+ return false;
+ }
+
+ DWORD flags = ((strcmp(scheme, "https") == 0) ? INTERNET_FLAG_SECURE : 0) |
+ INTERNET_FLAG_NO_COOKIES;
+ ScopedHInternet request(HttpOpenRequest(connection.get(), "POST", path,
+ /* lpszVersion */ NULL,
+ /* lpszReferer */ NULL,
+ /* lplpszAcceptTypes */ NULL,
+ flags,
+ /* dwContext */ NULL));
+
+ if (request.empty()) {
+ PINGSENDER_LOG("ERROR: Could not open HTTP POST request\n");
+ return false;
+ }
+
+ bool rv = HttpSendRequest(request.get(),
+ /* lpszHeaders */ nullptr,
+ /* dwHeadersLength */ 0,
+ (LPVOID)payload.c_str(),
+ payload.size());
+ if (!rv) {
+ PINGSENDER_LOG("ERROR: Could not execute HTTP POST request\n");
+ }
+
+ return rv;
+}
+
+} // namespace PingSender